diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c70760 --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +# Android Studio +*.iml +.gradle +/local.properties +<<<<<<< HEAD +/.idea/ +======= +/.idea +>>>>>>> pr-2-Aflame7121-kotlinTodoApp +.DS_Store +/build +/captures +.externalNativeBuild +.cxx + +<<<<<<< HEAD +# Kotlin/Android specific +bin/ +gen/ +out/ + +# Dependency directories +/node_modules +/app/build/ + +# Logs +*.log + +# Environment files +.env + +# Gradle +.gradle/ +build/ + +# VS Code +.vscode/ + +# MacOS +.DS_Store + +# Temp files +*.swp +*~ +======= +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Virtual machine crash logs +hs_err_pid* + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +/captures + +# IntelliJ +*.iml +.idea/ + +# 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 + +# Gradle files +.gradle/ +build/ +>>>>>>> pr-2-Aflame7121-kotlinTodoApp diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..2fdca57 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,86 @@ +plugins { + id("com.android.application") + id("kotlin-android") +<<<<<<< HEAD +======= + id("kotlin-kapt") +>>>>>>> pr-2-Aflame7121-kotlinTodoApp +} + +android { + compileSdk = 33 + defaultConfig { + applicationId = "com.todoapp" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + +<<<<<<< HEAD +======= + buildFeatures { + viewBinding = true + } + +>>>>>>> pr-2-Aflame7121-kotlinTodoApp + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } +<<<<<<< HEAD + +======= +>>>>>>> pr-2-Aflame7121-kotlinTodoApp + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +<<<<<<< HEAD + +======= +>>>>>>> pr-2-Aflame7121-kotlinTodoApp + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { +<<<<<<< HEAD + // 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.10.3") + testImplementation("androidx.test:core:1.5.0") + testImplementation("androidx.test.ext:junit:1.1.5") + testImplementation("org.mockito:mockito-core:4.8.0") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") +======= + // Android Core + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.8.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + + // ViewModel and LiveData + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1") + + // Testing + testImplementation("junit:junit:4.13.2") + testImplementation("androidx.test:core:1.5.0") + testImplementation("androidx.test.ext:junit:1.1.5") + testImplementation("org.robolectric:robolectric:4.9") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +>>>>>>> pr-2-Aflame7121-kotlinTodoApp +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/data/Todo.kt b/app/src/main/java/com/todoapp/data/Todo.kt new file mode 100644 index 0000000..41b6d83 --- /dev/null +++ b/app/src/main/java/com/todoapp/data/Todo.kt @@ -0,0 +1,9 @@ +package com.todoapp.data + +data class Todo( + val id: Long = 0, + val title: String, + val description: String? = null, + val isCompleted: Boolean = false, + val createdAt: Long = System.currentTimeMillis() +) \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/model/TodoItem.kt b/app/src/main/java/com/todoapp/model/TodoItem.kt new file mode 100644 index 0000000..70dc95d --- /dev/null +++ b/app/src/main/java/com/todoapp/model/TodoItem.kt @@ -0,0 +1,10 @@ +package com.todoapp.model + +import java.util.Date + +data class TodoItem( + val id: Long, + val title: String, + val dueDate: Date? = null, + val isCompleted: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/ui/MainActivity.kt b/app/src/main/java/com/todoapp/ui/MainActivity.kt new file mode 100644 index 0000000..aaedab8 --- /dev/null +++ b/app/src/main/java/com/todoapp/ui/MainActivity.kt @@ -0,0 +1,67 @@ +package com.todoapp.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import com.todoapp.R +import com.todoapp.databinding.ActivityMainBinding +import com.todoapp.model.TodoItem +import java.util.Date + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + private lateinit var todoViewModel: TodoViewModel + private lateinit var todoListAdapter: TodoListAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Inflate the layout using view binding + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + // Initialize ViewModel + todoViewModel = ViewModelProvider(this).get(TodoViewModel::class.java) + + // Setup RecyclerView + setupRecyclerView() + + // Observe todo items and update adapter + todoViewModel.todoItems.observe(this) { todos -> + todoListAdapter.submitList(todos) + } + + // Setup Add Todo button + binding.addTodoButton.setOnClickListener { + val todoText = binding.todoEditText.text.toString().trim() + if (todoText.isNotEmpty()) { + val newTodo = TodoItem( + id = System.currentTimeMillis(), // Unique ID + title = todoText, + dueDate = Date(), // Current date + isCompleted = false + ) + todoViewModel.addTodo(newTodo) + binding.todoEditText.text.clear() + } + } + } + + private fun setupRecyclerView() { + todoListAdapter = TodoListAdapter( + onItemClick = { todo -> + // Edit todo - for now, just print or log + println("Clicked todo: $todo") + }, + onCompletionToggle = { todo -> + todoViewModel.toggleTodoCompletion(todo) + } + ) + + binding.todoRecyclerView.apply { + layoutManager = LinearLayoutManager(this@MainActivity) + adapter = todoListAdapter + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/ui/TodoAdapter.kt b/app/src/main/java/com/todoapp/ui/TodoAdapter.kt new file mode 100644 index 0000000..6ee85df --- /dev/null +++ b/app/src/main/java/com/todoapp/ui/TodoAdapter.kt @@ -0,0 +1,52 @@ +package com.todoapp.ui + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.todoapp.data.Todo +import com.todoapp.databinding.ItemTodoBinding + +class TodoAdapter( + private val onItemClick: (Todo) -> Unit, + private val onDeleteClick: (Todo) -> Unit, + private val onCompleteToggle: (Todo) -> Unit +) : ListAdapter(TodoDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder { + val binding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return TodoViewHolder(binding, onItemClick, onDeleteClick, onCompleteToggle) + } + + override fun onBindViewHolder(holder: TodoViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class TodoViewHolder( + private val binding: ItemTodoBinding, + private val onItemClick: (Todo) -> Unit, + private val onDeleteClick: (Todo) -> Unit, + private val onCompleteToggle: (Todo) -> Unit + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(todo: Todo) { + binding.todoTitleTextView.text = todo.title + binding.todoCheckBox.isChecked = todo.isCompleted + + // Set click listeners + binding.root.setOnClickListener { onItemClick(todo) } + binding.deleteButton.setOnClickListener { onDeleteClick(todo) } + binding.todoCheckBox.setOnCheckedChangeListener { _, _ -> onCompleteToggle(todo) } + } + } + + 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/ui/TodoViewModel.kt b/app/src/main/java/com/todoapp/ui/TodoViewModel.kt new file mode 100644 index 0000000..bd97a70 --- /dev/null +++ b/app/src/main/java/com/todoapp/ui/TodoViewModel.kt @@ -0,0 +1,47 @@ +package com.todoapp.ui + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.todoapp.model.TodoItem + +class TodoViewModel : ViewModel() { + // Mutable list of todo items + private val _todoItems = MutableLiveData>(emptyList()) + val todoItems: LiveData> = _todoItems + + // Add a new todo item + fun addTodo(todoItem: TodoItem) { + val currentList = _todoItems.value?.toMutableList() ?: mutableListOf() + currentList.add(todoItem) + _todoItems.value = currentList + } + + // Update an existing todo item + fun updateTodo(updatedTodoItem: TodoItem) { + val currentList = _todoItems.value?.toMutableList() ?: return + val index = currentList.indexOfFirst { it.id == updatedTodoItem.id } + if (index != -1) { + currentList[index] = updatedTodoItem + _todoItems.value = currentList + } + } + + // Delete a todo item + fun deleteTodo(todoItem: TodoItem) { + val currentList = _todoItems.value?.toMutableList() ?: return + currentList.removeAll { it.id == todoItem.id } + _todoItems.value = currentList + } + + // Toggle todo completion status + fun toggleTodoCompletion(todoItem: TodoItem) { + val currentList = _todoItems.value?.toMutableList() ?: return + val index = currentList.indexOfFirst { it.id == todoItem.id } + if (index != -1) { + val updatedTodoItem = currentList[index].copy(isCompleted = !currentList[index].isCompleted) + currentList[index] = updatedTodoItem + _todoItems.value = currentList + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/ui/adapter/TodoListAdapter.kt b/app/src/main/java/com/todoapp/ui/adapter/TodoListAdapter.kt new file mode 100644 index 0000000..b3e681d --- /dev/null +++ b/app/src/main/java/com/todoapp/ui/adapter/TodoListAdapter.kt @@ -0,0 +1,67 @@ +package com.todoapp.ui.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.TodoItem +import java.text.SimpleDateFormat +import java.util.Locale + +class TodoListAdapter( + private val onItemClick: (TodoItem) -> Unit, + private val onCompletionToggle: (TodoItem) -> Unit +) : ListAdapter(TodoItemDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoItemViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.todo_item_layout, parent, false) + return TodoItemViewHolder(view, onItemClick, onCompletionToggle) + } + + override fun onBindViewHolder(holder: TodoItemViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class TodoItemViewHolder( + itemView: View, + private val onItemClick: (TodoItem) -> Unit, + private val onCompletionToggle: (TodoItem) -> Unit + ) : RecyclerView.ViewHolder(itemView) { + private val titleTextView: TextView = itemView.findViewById(R.id.todo_title) + private val dueDateTextView: TextView = itemView.findViewById(R.id.todo_due_date) + private val completionCheckBox: CheckBox = itemView.findViewById(R.id.todo_completion_checkbox) + + fun bind(todoItem: TodoItem) { + titleTextView.text = todoItem.title + completionCheckBox.isChecked = todoItem.isCompleted + + // Handle due date + if (todoItem.dueDate != null) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + dueDateTextView.text = dateFormat.format(todoItem.dueDate) + dueDateTextView.visibility = View.VISIBLE + } else { + dueDateTextView.visibility = View.GONE + } + + // Set click listeners + itemView.setOnClickListener { onItemClick(todoItem) } + completionCheckBox.setOnCheckedChangeListener { _, _ -> onCompletionToggle(todoItem) } + } + } + + class TodoItemDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TodoItem, newItem: TodoItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: TodoItem, newItem: TodoItem): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1b51bf9 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,32 @@ + + + + + + + + + +