Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")
}
59 changes: 59 additions & 0 deletions app/src/main/java/com/todoapp/adapter/TodoListAdapter.kt
Original file line number Diff line number Diff line change
@@ -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<Todo, TodoListAdapter.TodoViewHolder>(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<Todo>() {
override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean {
return oldItem == newItem
}
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/com/todoapp/model/Todo.kt
Original file line number Diff line number Diff line change
@@ -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
)
22 changes: 22 additions & 0 deletions app/src/main/res/layout/item_todo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">

<CheckBox
android:id="@+id/todo_completed_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp" />

<TextView
android:id="@+id/todo_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp" />

</LinearLayout>
67 changes: 67 additions & 0 deletions app/src/test/java/com/todoapp/adapter/TodoListAdapterTest.kt
Original file line number Diff line number Diff line change
@@ -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<View>(R.id.todo_completed_checkbox)
checkBox.performClick()

assertEquals(todo, toggledTodo)
assertEquals(true, toggledStatus)
}
}