Skip to content
Open
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
73 changes: 73 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Android Studio
*.iml
.gradle
/local.properties
<<<<<<< HEAD
/.idea
=======
/.idea/
>>>>>>> pr-3-gapcomputer-kotlinTodoApp
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

<<<<<<< HEAD
# Gradle files
gradle/
gradlew
gradlew.bat

# Compiled class files
*.class

# Log files
*.log

# IntelliJ
out/

# Kotlin build files
build/
*.hprof

# Virtual machine crash logs
hs_err_pid*

# Dependency directories
node_modules/

# Local configuration file
local.properties

# Android Profiling
*.hprof
=======
# Kotlin and Android
bin/
out/
*.class
*.log

# Dependency directories
/node_modules
/app/build/

# Sensitive or local files
local.properties
.env

# Temp files
*.swp
*~
.tmp

# Gradle
.gradle/
build/

# Logs
logs/
*.log
>>>>>>> pr-3-gapcomputer-kotlinTodoApp
59 changes: 59 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}

android {
compileSdk = 33

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

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

buildFeatures {
viewBinding = true
}

testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}

dependencies {
// Kotlin standard library
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.0")

// AndroidX
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.0")

// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.9")
testImplementation("org.mockito:mockito-core:4.8.0")
testImplementation("androidx.test:core:1.5.0")
testImplementation("androidx.test.ext:junit:1.1.5")

// Android Testing
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

tasks.withType<Test> {
configure<JacocoTaskExtension> {
isEnabled = true
}

// Ensure tests run with Robolectric
useJUnitPlatform()
}
8 changes: 8 additions & 0 deletions app/src/main/java/com/example/todoapp/data/model/Todo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.todoapp.data.model

data class Todo(
val id: Long,
val title: String,
val description: String,
val isCompleted: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.example.todoapp.ui.adapter

import android.app.AlertDialog
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.todoapp.R
import com.example.todoapp.data.model.Todo
import com.example.todoapp.databinding.ItemTodoBinding
import com.example.todoapp.ui.viewmodel.TodoViewModel

/**
* RecyclerView adapter for displaying and managing Todo items
*
* @property context Android context for showing dialogs
* @property viewModel ViewModel for handling Todo item operations
* @property todoList List of Todo items to display
*/
class TodoListAdapter(
private val context: Context,
private val viewModel: TodoViewModel,
private var todoList: List<Todo>
) : RecyclerView.Adapter<TodoListAdapter.TodoViewHolder>() {

/**
* Shows a confirmation dialog before deleting a Todo item
*
* @param todo The Todo item to be deleted
*/
private fun showDeleteConfirmationDialog(todo: Todo) {
AlertDialog.Builder(context)
.setTitle(R.string.delete_todo_title)
.setMessage(R.string.delete_todo_message)
.setPositiveButton(R.string.delete) { dialog, _ ->;
// Confirm deletion
viewModel.deleteTodo(todo)
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->;
// Cancel deletion
dialog.dismiss()
}
.create()
.show()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
val binding = ItemTodoBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return TodoViewHolder(binding)
}

override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val todo = todoList[position]
holder.bind(todo)
}

override fun getItemCount(): Int = todoList.size

inner class TodoViewHolder(private val binding: ItemTodoBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(todo: Todo) {
binding.apply {
// Set todo details
todoTitle.text = todo.title
todoDescription.text = todo.description

// Delete button click listener with confirmation dialog
deleteButton.setOnClickListener {
showDeleteConfirmationDialog(todo)
}
}
}
}

/**
* Update the list of Todo items and notify the adapter
*
* @param newList New list of Todo items
*/
fun updateList(newList: List<Todo>) {
todoList = newList
notifyDataSetChanged()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.todoapp.ui.viewmodel

import com.example.todoapp.data.model.Todo

class TodoViewModel {
private val todos = mutableListOf<Todo>()

fun deleteTodo(todo: Todo) {
todos.remove(todo)
}

fun getTodos(): List<Todo> = todos.toList()
}
11 changes: 11 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General app strings -->
<string name="app_name">Todo App</string>

<!-- Delete Confirmation Dialog Strings -->
<string name="delete_todo_title">Delete Todo</string>
<string name="delete_todo_message">Are you sure you want to delete this todo item?</string>
<string name="delete">Delete</string>
<string name="cancel">Cancel</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.todoapp.ui.adapter

import android.app.AlertDialog
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.example.todoapp.data.model.Todo
import com.example.todoapp.ui.viewmodel.TodoViewModel
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class TodoListAdapterTest {

private lateinit var context: Context

@Mock
private lateinit var mockViewModel: TodoViewModel

private lateinit var todoListAdapter: TodoListAdapter

@Before
fun setup() {
MockitoAnnotations.openMocks(this)
context = ApplicationProvider.getApplicationContext()

val todoList = listOf(
Todo(1, "Test Todo 1", "Description 1", false),
Todo(2, "Test Todo 2", "Description 2", true)
)

todoListAdapter = TodoListAdapter(context, mockViewModel, todoList)
}

@Test
fun `adapter initializes with correct item count`() {
assertEquals(2, todoListAdapter.itemCount)
}

@Test
fun `update list changes item count`() {
val newList = listOf(
Todo(3, "New Todo", "New Description", false)
)
todoListAdapter.updateList(newList)
assertEquals(1, todoListAdapter.itemCount)
}

@Test
fun `delete confirmation dialog can be created`() {
val todo = Todo(1, "Test Todo", "Description", false)
val alertDialog = AlertDialog.Builder(context)
.setTitle("Delete Todo")
.setMessage("Are you sure?")
.setPositiveButton("Delete") { dialog, _ ->
mockViewModel.deleteTodo(todo)
dialog.dismiss()
}
.setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() }
.create()

assert(alertDialog != null)
}
}
Loading