diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..858e1fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/ +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/jarRepositories.xml + +# Keystore files +*.jks +*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +lint/reports/ + +# Android Profiling +*.hprof \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..4a7bb22 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("com.android.application") + id("kotlin-android") +} + +android { + compileSdk = 33 + + defaultConfig { + applicationId = "com.todoapp" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.recyclerview:recyclerview:1.3.0") + + // Testing dependencies + testImplementation("junit:junit:4.13.2") + testImplementation("org.mockito:mockito-core:4.8.0") + testImplementation("org.robolectric:robolectric:4.9") + + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/adapter/TodoListAdapter.kt b/app/src/main/java/com/todoapp/adapter/TodoListAdapter.kt new file mode 100644 index 0000000..ff304a5 --- /dev/null +++ b/app/src/main/java/com/todoapp/adapter/TodoListAdapter.kt @@ -0,0 +1,59 @@ +package com.todoapp.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.todoapp.R +import com.todoapp.model.Todo + +class TodoListAdapter( + private val onItemClick: (Todo) -> Unit, + private val onCompletionToggle: (Todo, Boolean) -> Unit +) : ListAdapter(TodoDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_todo, parent, false) + return TodoViewHolder(view, onItemClick, onCompletionToggle) + } + + override fun onBindViewHolder(holder: TodoViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class TodoViewHolder( + itemView: View, + private val onItemClick: (Todo) -> Unit, + private val onCompletionToggle: (Todo, Boolean) -> Unit + ) : RecyclerView.ViewHolder(itemView) { + private val titleTextView: TextView = itemView.findViewById(R.id.todo_title) + private val completedCheckBox: CheckBox = itemView.findViewById(R.id.todo_completed_checkbox) + + fun bind(todo: Todo) { + titleTextView.text = todo.title + completedCheckBox.isChecked = todo.isCompleted + + itemView.setOnClickListener { onItemClick(todo) } + + completedCheckBox.setOnCheckedChangeListener { _, isChecked -> + onCompletionToggle(todo, isChecked) + } + } + } + + // DiffUtil for efficient list updates + class TodoDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/model/Todo.kt b/app/src/main/java/com/todoapp/model/Todo.kt new file mode 100644 index 0000000..a0d4af5 --- /dev/null +++ b/app/src/main/java/com/todoapp/model/Todo.kt @@ -0,0 +1,8 @@ +package com.todoapp.model + +data class Todo( + val id: Long = 0, + val title: String, + val description: String? = null, + val isCompleted: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/res/layout/item_todo.xml b/app/src/main/res/layout/item_todo.xml new file mode 100644 index 0000000..c8e2618 --- /dev/null +++ b/app/src/main/res/layout/item_todo.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/todoapp/adapter/TodoListAdapterTest.kt b/app/src/test/java/com/todoapp/adapter/TodoListAdapterTest.kt new file mode 100644 index 0000000..a16c4f1 --- /dev/null +++ b/app/src/test/java/com/todoapp/adapter/TodoListAdapterTest.kt @@ -0,0 +1,67 @@ +package com.todoapp.adapter + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.todoapp.R +import com.todoapp.model.Todo +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class TodoListAdapterTest { + + private lateinit var adapter: TodoListAdapter + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + adapter = TodoListAdapter( + onItemClick = {}, + onCompletionToggle = { _, _ -> } + ) + } + + @Test + fun testAdapterItemCount() { + val todos = listOf( + Todo(1, "Test Todo 1"), + Todo(2, "Test Todo 2") + ) + adapter.submitList(todos) + assertEquals(2, adapter.itemCount) + } + + @Test + fun testTodoCompletionToggle() { + var toggledTodo: Todo? = null + var toggledStatus: Boolean? = null + + adapter = TodoListAdapter( + onItemClick = {}, + onCompletionToggle = { todo, isCompleted -> + toggledTodo = todo + toggledStatus = isCompleted + } + ) + + val todo = Todo(1, "Test Todo", isCompleted = false) + adapter.submitList(listOf(todo)) + + val viewHolder = adapter.onCreateViewHolder( + RecyclerView(RuntimeEnvironment.getApplication()), + 0 + ) + + val checkBox = viewHolder.itemView.findViewById(R.id.todo_completed_checkbox) + checkBox.performClick() + + assertEquals(todo, toggledTodo) + assertEquals(true, toggledStatus) + } +} \ No newline at end of file