Skip to content
44 changes: 44 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Gradle files
.gradle/
build/

# Local configuration file
local.properties

# Android Studio generated files
*.iml
.idea/

# Compiled class files
*.class

# Log files
*.log

# Android generated files
bin/
gen/
out/

# Dependency directories
/captures
.externalNativeBuild
.cxx

# macOS system files
.DS_Store

# Backup files
*.bak
*.swp

# Kotlin build artifacts
*.jar
*.war
*.ear

# Virtual machine crash logs
hs_err_pid*

# Android Profiling
*.hprof
55 changes: 55 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
}

android {
namespace = "com.todoapp"
compileSdk = 33

defaultConfig {
applicationId = "com.todoapp"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

dependencies {
// Room Database
implementation("androidx.room:room-runtime:2.5.1")
implementation("androidx.room:room-ktx:2.5.1")
kapt("androidx.room:room-compiler:2.5.1")

// Kotlin standard library
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.20")

// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20")

// Android testing
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
50 changes: 50 additions & 0 deletions app/src/main/java/com/todoapp/data/entity/Todo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.todoapp.data.entity

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.time.LocalDateTime

/**
* Room database entity representing a Todo item.
*
* @property id Unique identifier for the Todo item
* @property title Title of the Todo item
* @property description Detailed description of the Todo item
* @property isCompleted Indicates whether the Todo item is completed
* @property createdAt Timestamp of when the Todo item was created
* @property dueDate Optional due date for the Todo item
*/
@Entity(tableName = "todos")
data class Todo(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,

@ColumnInfo(name = "title")
val title: String,

@ColumnInfo(name = "description")
val description: String? = null,

@ColumnInfo(name = "is_completed")
val isCompleted: Boolean = false,

@ColumnInfo(name = "created_at")
val createdAt: LocalDateTime = LocalDateTime.now(),

@ColumnInfo(name = "due_date")
val dueDate: LocalDateTime? = null
) {
/**
* Validates the Todo item properties.
*
* @throws IllegalArgumentException if validation fails
*/
init {
require(title.isNotBlank()) { "Title cannot be blank" }
require(title.length <= 100) { "Title cannot exceed 100 characters" }
description?.let {
require(it.length <= 500) { "Description cannot exceed 500 characters" }
}
}
}
66 changes: 66 additions & 0 deletions app/src/test/java/com/todoapp/data/entity/TodoTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.todoapp.data.entity

import org.junit.Test
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull

class TodoTest {

@Test
fun `create todo with valid data`() {
val todo = Todo(
title = "Test Todo",
description = "Test description"
)

assertNotNull(todo)
assertEquals("Test Todo", todo.title)
assertEquals("Test description", todo.description)
assertEquals(false, todo.isCompleted)
assertNotNull(todo.createdAt)
}

@Test
fun `create todo with all properties`() {
val dueDate = LocalDateTime.now().plusDays(1)
val todo = Todo(
title = "Complete project",
description = "Finish the todo app implementation",
isCompleted = true,
dueDate = dueDate
)

assertEquals("Complete project", todo.title)
assertEquals("Finish the todo app implementation", todo.description)
assertEquals(true, todo.isCompleted)
assertEquals(dueDate, todo.dueDate)
}

@Test
fun `fail to create todo with blank title`() {
assertFailsWith<IllegalArgumentException> {
Todo(title = "")
}
}

@Test
fun `fail to create todo with title exceeding 100 characters`() {
val longTitle = "a".repeat(101)
assertFailsWith<IllegalArgumentException> {
Todo(title = longTitle)
}
}

@Test
fun `fail to create todo with description exceeding 500 characters`() {
val longDescription = "a".repeat(501)
assertFailsWith<IllegalArgumentException> {
Todo(
title = "Valid Title",
description = longDescription
)
}
}
}
31 changes: 31 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
// Version variables for consistent dependency management
room_version = "2.5.2"
kotlin_version = "1.8.20"
gradle_version = "8.1.0"
hilt_version = "2.44"
}

repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:$gradle_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}

plugins {
id 'com.android.application' version '8.1.0' apply false
id 'com.android.library' version '8.1.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
}

task clean(type: Delete) {
delete rootProject.buildDir
}