fix gitignore
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.ksp)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.todokotlin"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.todokotlin"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.room.ktx)
|
||||
ksp(libs.room.compiler)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="Todo Kotlin"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.TodoKotlin">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.TodoKotlin">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.todokotlin
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.example.todokotlin.ui.TodoApp
|
||||
import com.example.todokotlin.ui.theme.TodoKotlinTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
TodoKotlinTheme {
|
||||
val viewModel: TodoViewModel = viewModel()
|
||||
TodoApp(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.example.todokotlin
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.example.todokotlin.data.Todo
|
||||
import com.example.todokotlin.data.TodoDatabase
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class TodoFilter { All, Active, Done }
|
||||
|
||||
data class TodoUiState(
|
||||
val todos: List<Todo> = emptyList(),
|
||||
val filter: TodoFilter = TodoFilter.All,
|
||||
val allCount: Int = 0,
|
||||
val activeCount: Int = 0,
|
||||
val doneCount: Int = 0
|
||||
)
|
||||
|
||||
class TodoViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val dao = TodoDatabase.getDatabase(application).todoDao()
|
||||
private val _filter = MutableStateFlow(TodoFilter.All)
|
||||
|
||||
val uiState: StateFlow<TodoUiState> = combine(
|
||||
dao.getAllTodos(),
|
||||
_filter
|
||||
) { todos, filter ->
|
||||
val filtered = when (filter) {
|
||||
TodoFilter.All -> todos
|
||||
TodoFilter.Active -> todos.filter { !it.completed }
|
||||
TodoFilter.Done -> todos.filter { it.completed }
|
||||
}
|
||||
TodoUiState(
|
||||
todos = filtered,
|
||||
filter = filter,
|
||||
allCount = todos.size,
|
||||
activeCount = todos.count { !it.completed },
|
||||
doneCount = todos.count { it.completed }
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5000),
|
||||
initialValue = TodoUiState()
|
||||
)
|
||||
|
||||
fun setFilter(filter: TodoFilter) {
|
||||
_filter.value = filter
|
||||
}
|
||||
|
||||
fun addTodo(title: String, category: String, priority: String) {
|
||||
viewModelScope.launch {
|
||||
dao.insert(
|
||||
Todo(
|
||||
title = title,
|
||||
category = category,
|
||||
priority = priority
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTodo(todo: Todo) {
|
||||
viewModelScope.launch {
|
||||
dao.update(todo)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleCompleted(todo: Todo) {
|
||||
viewModelScope.launch {
|
||||
dao.update(todo.copy(completed = !todo.completed))
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteTodo(todo: Todo) {
|
||||
viewModelScope.launch {
|
||||
dao.delete(todo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.example.todokotlin.data
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "todos")
|
||||
data class Todo(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
val title: String,
|
||||
val completed: Boolean = false,
|
||||
val category: String = "Personal",
|
||||
val priority: String = "Medium",
|
||||
val createdAt: Long = System.currentTimeMillis()
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.example.todokotlin.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface TodoDao {
|
||||
@Query("SELECT * FROM todos ORDER BY createdAt DESC")
|
||||
fun getAllTodos(): Flow<List<Todo>>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(todo: Todo)
|
||||
|
||||
@Update
|
||||
suspend fun update(todo: Todo)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(todo: Todo)
|
||||
|
||||
@Query("DELETE FROM todos WHERE id = :id")
|
||||
suspend fun deleteById(id: Long)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.example.todokotlin.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(entities = [Todo::class], version = 1, exportSchema = false)
|
||||
abstract class TodoDatabase : RoomDatabase() {
|
||||
abstract fun todoDao(): TodoDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: TodoDatabase? = null
|
||||
|
||||
fun getDatabase(context: Context): TodoDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
TodoDatabase::class.java,
|
||||
"todo_database"
|
||||
).build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val categories = listOf("Personal", "Work", "Shopping", "Health", "Learning")
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun CategoryPicker(
|
||||
selected: String,
|
||||
onSelect: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = "Category",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
categories.forEach { category ->
|
||||
FilterChip(
|
||||
selected = selected == category,
|
||||
onClick = { onSelect(category) },
|
||||
label = { Text(category) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun EmptyState(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = "No todos yet",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Tap the + button to add your first todo",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.todokotlin.TodoFilter
|
||||
|
||||
@Composable
|
||||
fun FilterBar(
|
||||
currentFilter: TodoFilter,
|
||||
allCount: Int,
|
||||
activeCount: Int,
|
||||
doneCount: Int,
|
||||
onFilterChange: (TodoFilter) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
FilterChip(
|
||||
selected = currentFilter == TodoFilter.All,
|
||||
onClick = { onFilterChange(TodoFilter.All) },
|
||||
label = { Text("All ($allCount)") }
|
||||
)
|
||||
FilterChip(
|
||||
selected = currentFilter == TodoFilter.Active,
|
||||
onClick = { onFilterChange(TodoFilter.Active) },
|
||||
label = { Text("Active ($activeCount)") }
|
||||
)
|
||||
FilterChip(
|
||||
selected = currentFilter == TodoFilter.Done,
|
||||
onClick = { onFilterChange(TodoFilter.Done) },
|
||||
label = { Text("Done ($doneCount)") }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
data class PriorityOption(val name: String, val color: Color)
|
||||
|
||||
val priorities = listOf(
|
||||
PriorityOption("Low", Color(0xFF43A047)),
|
||||
PriorityOption("Medium", Color(0xFFFB8C00)),
|
||||
PriorityOption("High", Color(0xFFE53935))
|
||||
)
|
||||
|
||||
fun priorityColor(priority: String): Color {
|
||||
return priorities.find { it.name == priority }?.color ?: Color(0xFFFB8C00)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PriorityPicker(
|
||||
selected: String,
|
||||
onSelect: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = "Priority",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
priorities.forEach { priority ->
|
||||
FilterChip(
|
||||
selected = selected == priority.name,
|
||||
onClick = { onSelect(priority.name) },
|
||||
label = { Text(priority.name) },
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
selectedContainerColor = priority.color.copy(alpha = 0.2f),
|
||||
selectedLabelColor = priority.color
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.example.todokotlin.TodoViewModel
|
||||
import com.example.todokotlin.data.Todo
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TodoApp(viewModel: TodoViewModel) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
var showAddDialog by remember { mutableStateOf(false) }
|
||||
var editingTodo by remember { mutableStateOf<Todo?>(null) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text("Todos \u2013 Kotlin Android")
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = { showAddDialog = true },
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = "Add Todo",
|
||||
tint = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(modifier = Modifier.padding(innerPadding)) {
|
||||
FilterBar(
|
||||
currentFilter = uiState.filter,
|
||||
allCount = uiState.allCount,
|
||||
activeCount = uiState.activeCount,
|
||||
doneCount = uiState.doneCount,
|
||||
onFilterChange = { viewModel.setFilter(it) }
|
||||
)
|
||||
|
||||
if (uiState.todos.isEmpty()) {
|
||||
EmptyState()
|
||||
} else {
|
||||
TodoList(
|
||||
todos = uiState.todos,
|
||||
onToggle = { viewModel.toggleCompleted(it) },
|
||||
onTap = { editingTodo = it },
|
||||
onDelete = { viewModel.deleteTodo(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showAddDialog) {
|
||||
TodoFormDialog(
|
||||
onDismiss = { showAddDialog = false },
|
||||
onSave = { title, category, priority ->
|
||||
viewModel.addTodo(title, category, priority)
|
||||
showAddDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
editingTodo?.let { todo ->
|
||||
TodoFormDialog(
|
||||
todo = todo,
|
||||
onDismiss = { editingTodo = null },
|
||||
onSave = { title, category, priority ->
|
||||
viewModel.updateTodo(
|
||||
todo.copy(
|
||||
title = title,
|
||||
category = category,
|
||||
priority = priority
|
||||
)
|
||||
)
|
||||
editingTodo = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.todokotlin.data.Todo
|
||||
|
||||
@Composable
|
||||
fun TodoFormDialog(
|
||||
todo: Todo? = null,
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (title: String, category: String, priority: String) -> Unit
|
||||
) {
|
||||
var title by remember { mutableStateOf(todo?.title ?: "") }
|
||||
var category by remember { mutableStateOf(todo?.category ?: "Personal") }
|
||||
var priority by remember { mutableStateOf(todo?.priority ?: "Medium") }
|
||||
|
||||
val isEditing = todo != null
|
||||
val dialogTitle = if (isEditing) "Edit Todo" else "Add Todo"
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(dialogTitle) },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = title,
|
||||
onValueChange = { title = it },
|
||||
label = { Text("Title") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
CategoryPicker(
|
||||
selected = category,
|
||||
onSelect = { category = it }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
PriorityPicker(
|
||||
selected = priority,
|
||||
onSelect = { priority = it }
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
if (title.isNotBlank()) {
|
||||
onSave(title.trim(), category, priority)
|
||||
}
|
||||
},
|
||||
enabled = title.isNotBlank()
|
||||
) {
|
||||
Text("Save")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SuggestionChip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.todokotlin.data.Todo
|
||||
|
||||
@Composable
|
||||
fun TodoItem(
|
||||
todo: Todo,
|
||||
onToggle: () -> Unit,
|
||||
onTap: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onTap() },
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = todo.completed,
|
||||
onCheckedChange = { onToggle() }
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = todo.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textDecoration = if (todo.completed) TextDecoration.LineThrough else TextDecoration.None,
|
||||
color = if (todo.completed)
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
else
|
||||
MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
SuggestionChip(
|
||||
onClick = { },
|
||||
label = {
|
||||
Text(
|
||||
text = todo.category,
|
||||
style = MaterialTheme.typography.labelSmall
|
||||
)
|
||||
}
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.clip(CircleShape)
|
||||
.background(priorityColor(todo.priority))
|
||||
)
|
||||
Text(
|
||||
text = todo.priority,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = priorityColor(todo.priority)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.example.todokotlin.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SwipeToDismissBox
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.todokotlin.data.Todo
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TodoList(
|
||||
todos: List<Todo>,
|
||||
onToggle: (Todo) -> Unit,
|
||||
onTap: (Todo) -> Unit,
|
||||
onDelete: (Todo) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
items(
|
||||
items = todos,
|
||||
key = { it.id }
|
||||
) { todo ->
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = { value ->
|
||||
if (value == SwipeToDismissBoxValue.EndToStart) {
|
||||
onDelete(todo)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
SwipeToDismissBox(
|
||||
state = dismissState,
|
||||
backgroundContent = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFE53935))
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Delete",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
enableDismissFromStartToEnd = false,
|
||||
enableDismissFromEndToStart = true
|
||||
) {
|
||||
TodoItem(
|
||||
todo = todo,
|
||||
onToggle = { onToggle(todo) },
|
||||
onTap = { onTap(todo) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.example.todokotlin.ui.theme
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Color(0xFF6200EE),
|
||||
onPrimary = Color.White,
|
||||
primaryContainer = Color(0xFFE8DEF8),
|
||||
onPrimaryContainer = Color(0xFF21005D),
|
||||
secondary = Color(0xFF03DAC6),
|
||||
onSecondary = Color.Black,
|
||||
background = Color(0xFFFFFBFE),
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
surfaceVariant = Color(0xFFE7E0EC),
|
||||
onSurfaceVariant = Color(0xFF49454F)
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun TodoKotlinTheme(content: @Composable () -> Unit) {
|
||||
MaterialTheme(
|
||||
colorScheme = LightColorScheme,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.TodoKotlin" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
Reference in New Issue
Block a user