From fa11b3d67437786e68593c513ab0c0dddcd86454 Mon Sep 17 00:00:00 2001 From: Juan Gaines Date: Thu, 17 Oct 2024 19:54:33 -0500 Subject: [PATCH 1/3] feat: task screen --- .../com/juandgaines/todoapp/MainActivity.kt | 3 +- .../presentation/screens/detail/TaskScreen.kt | 281 ++++++++++++++++++ .../detail/components/TDAppTextField.kt | 95 ++++++ .../TaskScreenStatePreviewProvider.kt | 42 +++ .../screens/home/HomeScreenViewModel.kt | 3 - app/src/main/res/values/strings.xml | 6 + 6 files changed, 426 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/TaskScreen.kt create mode 100644 app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt create mode 100644 app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/providers/TaskScreenStatePreviewProvider.kt diff --git a/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt b/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt index 56dd795..942141e 100644 --- a/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt +++ b/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import com.juandgaines.todoapp.presentation.screens.detail.TaskScreen import com.juandgaines.todoapp.presentation.screens.home.HomeScreenRoot import com.juandgaines.todoapp.ui.theme.TodoAppTheme @@ -13,7 +14,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { TodoAppTheme() { - HomeScreenRoot() + TaskScreen() } } } diff --git a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/TaskScreen.kt b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/TaskScreen.kt new file mode 100644 index 0000000..7fd5321 --- /dev/null +++ b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/TaskScreen.kt @@ -0,0 +1,281 @@ +package com.juandgaines.todoapp.presentation.screens.detail + +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.ExperimentalMaterial3Api +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.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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextAlign.Companion +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import com.juandgaines.todoapp.R +import com.juandgaines.todoapp.domain.Category +import com.juandgaines.todoapp.presentation.screens.detail.providers.TaskScreenStatePreviewProvider +import com.juandgaines.todoapp.ui.theme.TodoAppTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TaskScreen( + modifier: Modifier = Modifier, + state: TaskScreenState +) { + + var isDescriptionFocus by remember { + mutableStateOf(false) + } + var isExpanded by remember { + mutableStateOf(false) + } + + Scaffold ( + topBar = { + TopAppBar( + title = { + Text( + style = MaterialTheme.typography.headlineSmall, + text = stringResource(R.string.task) + ) + } + ) + } + ){ padding-> + Column ( + verticalArrangement = Arrangement.spacedBy( + 8.dp + ), + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 16.dp) + ){ + Row ( + verticalAlignment = Alignment.CenterVertically + ){ + Text( + text = stringResource(R.string.done), + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ), + modifier = Modifier.padding(8.dp) + ) + Checkbox( + checked = state.isTaskDone, + onCheckedChange = { + + }, + ) + Spacer( + modifier = Modifier.weight(1f) + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clickable { + isExpanded = true + } + ){ + + Text( + text = state.category?.toString() ?: stringResource(R.string.category), + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ), + modifier = Modifier.border( + width = 1.dp, + color = MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(8.dp) + ) + .padding(8.dp) + ) + Box ( + modifier = Modifier.padding(8.dp), + contentAlignment = Alignment.Center + ){ + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = "Add Task", + tint = MaterialTheme.colorScheme.onSurface, + ) + DropdownMenu( + modifier = Modifier.background( + color = MaterialTheme.colorScheme.surfaceContainerHighest + ), + expanded = isExpanded, + onDismissRequest = { isExpanded = false } + ) { + Column { + Category.entries.forEach { category -> + Text( + text = category.name, + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ), + modifier = Modifier.padding(8.dp).padding( + 8.dp + ).clickable { + + } + ) + } + } + } + } + } + + + } + + BasicTextField( + value = state.taskName, + textStyle = MaterialTheme.typography.headlineLarge.copy( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold + ), + maxLines = 1, + onValueChange = { + + }, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + , + decorationBox = { innerBox -> + Column ( + modifier = Modifier.fillMaxWidth(), + ){ + if(state.taskName.isEmpty()){ + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.task_name), + color = MaterialTheme.colorScheme.onSurface.copy( + alpha = 0.5f + ), + style = MaterialTheme.typography.headlineLarge.copy( + fontWeight = FontWeight.Bold + ) + ) + } + else{ + innerBox() + } + } + } + ) + BasicTextField( + value = state.taskDescription ?: "", + textStyle = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onSurface + ), + maxLines = 15, + onValueChange = { + + }, + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { + isDescriptionFocus = it.isFocused + } + , + decorationBox = { innerBox -> + Column { + if(state.taskDescription.isNullOrEmpty() && !isDescriptionFocus){ + Text( + text = stringResource(R.string.task_description), + color = MaterialTheme.colorScheme.onSurface.copy( + alpha = 0.5f + ) + ) + }else{ + innerBox() + } + } + } + ) + Spacer( + modifier = Modifier.weight(1f) + ) + Button( + onClick = { }, + modifier = Modifier.fillMaxWidth() + .padding(46.dp) + ){ + Text( + text = stringResource(R.string.save), + style = MaterialTheme.typography.titleMedium.copy( + color = MaterialTheme.colorScheme.onPrimary + ) + ) + } + } + } +} + +@Composable +@Preview +fun TaskScreenLightPreview( + @PreviewParameter(TaskScreenStatePreviewProvider::class) state: TaskScreenState +){ + TodoAppTheme { + TaskScreen( + state = state + ) + } +} + +@Composable +@Preview( + uiMode = UI_MODE_NIGHT_YES +) +fun TaskScreenDarkPreview( + @PreviewParameter(TaskScreenStatePreviewProvider::class) state: TaskScreenState +){ + TodoAppTheme { + TaskScreen( + state = state + ) + } +} + + +data class TaskScreenState( + val taskName: String = "", + val taskDescription: String? = null, + val category:Category? = null, + val isTaskDone : Boolean = false, +) \ No newline at end of file diff --git a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt new file mode 100644 index 0000000..9ad21be --- /dev/null +++ b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt @@ -0,0 +1,95 @@ +package com.juandgaines.todoapp.presentation.screens.detail.components + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation.Companion.keyboardOptions +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp + +@Composable +fun TDAppTextField( + modifier: Modifier = Modifier, + state: TextFieldState, + hint:String, + lineLimits: TextFieldLineLimits = TextFieldLineLimits.SingleLine + +){ + var isFocused by remember { + mutableStateOf(false) + } + + Column( + modifier = modifier + ) { + BasicTextField( + state = state, + textStyle = MaterialTheme.typography.titleMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text + ), + lineLimits = lineLimits, + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .defaultMinSize(64.dp) + .border( + width = 1.dp, + color = if (isFocused) + MaterialTheme.colorScheme.outline + else + MaterialTheme.colorScheme.outlineVariant, + shape = RoundedCornerShape(8.dp) + ) + .padding(12.dp) + .onFocusChanged { + isFocused = it.isFocused + }, + decorator = { innerBox -> + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .weight(1f) + ) { + if(state.text.isEmpty() && !isFocused) { + Text( + text = hint, + color = MaterialTheme.colorScheme.onSurface.copy( + alpha = 0.5f + ), + modifier = Modifier.fillMaxWidth() + ) + } + innerBox() + } + } + } + ) + } +} diff --git a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/providers/TaskScreenStatePreviewProvider.kt b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/providers/TaskScreenStatePreviewProvider.kt new file mode 100644 index 0000000..20427b2 --- /dev/null +++ b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/providers/TaskScreenStatePreviewProvider.kt @@ -0,0 +1,42 @@ +package com.juandgaines.todoapp.presentation.screens.detail.providers + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.juandgaines.todoapp.domain.Category.OTHER +import com.juandgaines.todoapp.domain.Category.WORK +import com.juandgaines.todoapp.presentation.screens.detail.TaskScreenState + +class TaskScreenStatePreviewProvider:PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TaskScreenState( + taskName = "Task 1", + taskDescription = "Description 1", + isTaskDone = false, + category = WORK + ), + TaskScreenState( + taskName = "Task 2", + taskDescription = "Description 2", + isTaskDone = true, + category = WORK + ), + TaskScreenState( + taskName = "Task 3", + taskDescription = "Description 3", + isTaskDone = false, + category = OTHER + ), + TaskScreenState( + taskName = "Task 4", + taskDescription = "Description 4", + isTaskDone = true, + category = null + ), + TaskScreenState( + taskName = "Task 5", + taskDescription = null, + isTaskDone = false, + category = null + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/home/HomeScreenViewModel.kt b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/home/HomeScreenViewModel.kt index f4bc0b4..6969d7e 100644 --- a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/home/HomeScreenViewModel.kt +++ b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/home/HomeScreenViewModel.kt @@ -1,13 +1,11 @@ package com.juandgaines.todoapp.presentation.screens.home -import android.icu.text.DateFormat import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.juandgaines.todoapp.data.FakeTaskLocalDataSource -import com.juandgaines.todoapp.presentation.screens.home.HomeScreenAction.OnAddTask import com.juandgaines.todoapp.presentation.screens.home.HomeScreenAction.OnDeleteAllTasks import com.juandgaines.todoapp.presentation.screens.home.HomeScreenAction.OnDeleteTask import com.juandgaines.todoapp.presentation.screens.home.HomeScreenAction.OnToggleTask @@ -17,7 +15,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import java.time.LocalDate -import java.time.LocalDateTime import java.time.format.DateTimeFormatter class HomeScreenViewModel:ViewModel() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d100ea8..526e67e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,4 +7,10 @@ All tasked deleted Task updated You have %s pending task + Task name + Task description + Task + Completed: + Category + Save \ No newline at end of file From 992bba637705a374a18c7bfebaa3ed5cfbeb9cf1 Mon Sep 17 00:00:00 2001 From: Juan Gaines Date: Thu, 17 Oct 2024 19:55:00 -0500 Subject: [PATCH 2/3] feat: task screen --- app/src/main/java/com/juandgaines/todoapp/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt b/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt index 942141e..98cf6a1 100644 --- a/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt +++ b/app/src/main/java/com/juandgaines/todoapp/MainActivity.kt @@ -14,7 +14,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { TodoAppTheme() { - TaskScreen() + } } } From 5996527fba55a86515911d59022fb31a4e6564bd Mon Sep 17 00:00:00 2001 From: Juan Gaines Date: Thu, 17 Oct 2024 19:57:26 -0500 Subject: [PATCH 3/3] feat: task screen --- .../detail/components/TDAppTextField.kt | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt diff --git a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt b/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt deleted file mode 100644 index 9ad21be..0000000 --- a/app/src/main/java/com/juandgaines/todoapp/presentation/screens/detail/components/TDAppTextField.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.juandgaines.todoapp.presentation.screens.detail.components - -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.InputTransformation.Companion.keyboardOptions -import androidx.compose.foundation.text.input.TextFieldLineLimits -import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -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.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.unit.dp - -@Composable -fun TDAppTextField( - modifier: Modifier = Modifier, - state: TextFieldState, - hint:String, - lineLimits: TextFieldLineLimits = TextFieldLineLimits.SingleLine - -){ - var isFocused by remember { - mutableStateOf(false) - } - - Column( - modifier = modifier - ) { - BasicTextField( - state = state, - textStyle = MaterialTheme.typography.titleMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Text - ), - lineLimits = lineLimits, - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .defaultMinSize(64.dp) - .border( - width = 1.dp, - color = if (isFocused) - MaterialTheme.colorScheme.outline - else - MaterialTheme.colorScheme.outlineVariant, - shape = RoundedCornerShape(8.dp) - ) - .padding(12.dp) - .onFocusChanged { - isFocused = it.isFocused - }, - decorator = { innerBox -> - Row( - modifier = Modifier - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .weight(1f) - ) { - if(state.text.isEmpty() && !isFocused) { - Text( - text = hint, - color = MaterialTheme.colorScheme.onSurface.copy( - alpha = 0.5f - ), - modifier = Modifier.fillMaxWidth() - ) - } - innerBox() - } - } - } - ) - } -}