diff --git a/.vs/Lection5/FileContentIndex/156e9da7-35d6-4588-835c-5bd654859b61.vsidx b/.vs/Lection5/FileContentIndex/156e9da7-35d6-4588-835c-5bd654859b61.vsidx new file mode 100644 index 0000000..4ea579e Binary files /dev/null and b/.vs/Lection5/FileContentIndex/156e9da7-35d6-4588-835c-5bd654859b61.vsidx differ diff --git a/.vs/Lection5/v17/.wsuo b/.vs/Lection5/v17/.wsuo new file mode 100644 index 0000000..00070f6 Binary files /dev/null and b/.vs/Lection5/v17/.wsuo differ diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6b61141 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..ead5ee7 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 15a3f62..c58ec85 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3:1.1.2") implementation("com.google.firebase:protolite-well-known-types:18.0.0") + implementation("com.google.android.material:material:1.10.0") // Room val room_version = "2.5.2" diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/TaskEdit.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/TaskEdit.kt index 083cafb..c9c2e9e 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/TaskEdit.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/TaskEdit.kt @@ -1,14 +1,25 @@ package ru.ulstu.`is`.pmu.ui.task.edit import android.content.res.Configuration +import android.view.ContextThemeWrapper +import android.widget.CalendarView import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerFormatter +import androidx.compose.material3.DatePickerState import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -18,6 +29,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -25,14 +37,19 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import com.google.android.material.datepicker.MaterialDatePicker import java.util.Date import kotlinx.coroutines.launch import ru.ulstu.`is`.pmu.R @@ -40,6 +57,11 @@ import ru.ulstu.`is`.pmu.database.task.model.User import ru.ulstu.`is`.pmu.database.task.model.Task import ru.ulstu.`is`.pmu.ui.AppViewModelProvider import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.Calendar +import java.util.Locale @Composable fun TaskEdit( @@ -113,7 +135,6 @@ private fun UserDropDown( } } } - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun TaskEdit( @@ -144,13 +165,33 @@ private fun TaskEdit( label = { Text(stringResource(id = R.string.task_lastname)) }, singleLine = true ) + var showDatePicker by remember { mutableStateOf(false) } + if (showDatePicker) { + DatePicker( + onDateSelected = { selectedDate -> + onUpdate(taskUiState.taskDetails.copy(endDate = SimpleDateFormat("dd.MM.yyyy").format(selectedDate))) + showDatePicker = false + }, + onDismissRequest = { + showDatePicker = false + } + ) + } + + Button( + onClick = { showDatePicker = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Выбрать дату окончания") + } OutlinedTextField( modifier = Modifier.fillMaxWidth(), value = taskUiState.taskDetails.endDate, onValueChange = { newDate -> onUpdate(taskUiState.taskDetails.copy(endDate = newDate)) }, label = { Text(stringResource(id = R.string.task_endDate)) }, singleLine = true, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number) + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + enabled = false ) // UserDropDown( // userUiState = userUiState, @@ -169,7 +210,6 @@ private fun TaskEdit( } }, enabled = taskUiState.isEntryValid, - shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.task_save_button)) @@ -183,7 +223,7 @@ private fun TaskEdit( text = { Text("Введите дату по шаблону: 01.12.2023") }, confirmButton = { Button(onClick = { showInvalidDateDialog = false }) { - Text("ОК") + Text("Подтвердить") } } ) @@ -195,22 +235,99 @@ fun isValidDate(date: String): Boolean { return regex.matches(date) } -@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -fun TaskEditPreview() { - PmudemoTheme { - Surface( - color = MaterialTheme.colorScheme.background +fun CustomCalendarView(onDateSelected: (Date) -> Unit) { + AndroidView( + modifier = Modifier.wrapContentSize(), + factory = { context -> + // Используем стандартный контекст, без применения кастомной темы + CalendarView(context).apply { + // Настройки CalendarView + val calendar = Calendar.getInstance() + + setOnDateChangeListener { _, year, month, dayOfMonth -> + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, month) + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + onDateSelected(calendar.time) + } + } + } + ) +} + +@Composable +fun DatePicker(onDateSelected: (Date) -> Unit, onDismissRequest: () -> Unit) { + val selDate = remember { mutableStateOf(Date()) } + + //todo - add strings to resource after POC + Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) { + Column( + modifier = Modifier + .wrapContentSize() + .background( + color = Color.White + ) ) { - TaskEdit( - taskUiState = Task.getTask().toUiState(true), - userUiState = User.DEMO_User.toUiState(), - usersListUiState = UsersListUiState(listOf()), - onClick = {}, - onUpdate = {}, - onUserUpdate = {} - ) + Column( + Modifier + .defaultMinSize(minHeight = 72.dp) + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.primary + ) + .padding(16.dp) + ) { + Text( + text = "Выберите дату" + ) + + Spacer(modifier = Modifier.size(24.dp)) + + Text( + text = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()).format(selDate.value) + ) + + Spacer(modifier = Modifier.size(16.dp)) + } + + CustomCalendarView(onDateSelected = { + selDate.value = it + }) + + Spacer(modifier = Modifier.size(8.dp)) + + Row( + modifier = Modifier + .align(Alignment.End) + .padding(bottom = 16.dp, end = 16.dp) + .background( + color = Color.White, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) + ) { + TextButton( + onClick = onDismissRequest + ) { + //TODO - hardcode string + Text( + text = "Отмена" + ) + } + + TextButton( + onClick = { + onDateSelected(selDate.value) + onDismissRequest() + } + ) { + //TODO - hardcode string + Text( + text = "Подтвердить" + ) + } + + } } } } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskList.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskList.kt index b0cbec9..65f9ee6 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskList.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskList.kt @@ -73,7 +73,7 @@ fun TaskList( } } ) { innerPadding -> - TaskListContent( // Изменил имя, чтобы избежать путаницы с функцией TaskList + TaskListContent( modifier = Modifier .padding(innerPadding) .fillMaxSize(), @@ -82,12 +82,12 @@ fun TaskList( val route = Screen.TaskEdit.route.replace("{id}", uid.toString()) navController.navigate(route) }, - onSwipeLeft = { task: Task -> // Обработка свайпа влево (удаление) + onSwipeLeft = { task: Task -> coroutineScope.launch { viewModel.deleteTask(task) } }, - onSwipeRight = { task: Task -> // Обработка свайпа вправо (добавить в избранное) + onSwipeRight = { task: Task -> coroutineScope.launch { viewModel.favoriteTask(task) } @@ -102,20 +102,20 @@ private fun SwipeToAction( dismissState: DismissState, task: Task, onClick: (uid: Int) -> Unit, - onSwipeRight: (task: Task) -> Unit, // Для добавления в избранное - onSwipeLeft: (task: Task) -> Unit // Для удаления + onSwipeRight: (task: Task) -> Unit, + onSwipeLeft: (task: Task) -> Unit ) { SwipeToDismiss( state = dismissState, directions = setOf( - DismissDirection.EndToStart, // Для удаления - DismissDirection.StartToEnd // Для добавления в избранное + DismissDirection.EndToStart, + DismissDirection.StartToEnd ), background = { val backgroundColor by animateColorAsState( when (dismissState.targetValue) { DismissedToStart -> Color.Red.copy(alpha = 0.8f) - DismissValue.DismissedToEnd -> Color.Green.copy(alpha = 0.8f) // Цвет фона для избранного + DismissValue.DismissedToEnd -> Color.Green.copy(alpha = 0.8f) else -> Color.White }, label = "" ) @@ -132,7 +132,7 @@ private fun SwipeToAction( ) { Icon( modifier = Modifier.scale(iconScale), - imageVector = if (dismissState.targetValue == DismissedToStart) Icons.Outlined.Delete else Icons.Default.Star, // Иконка для избранного + imageVector = if (dismissState.targetValue == DismissedToStart) Icons.Outlined.Delete else Icons.Default.Star, contentDescription = if (dismissState.targetValue == DismissedToStart) "Delete" else "Favorite", tint = Color.White ) @@ -160,7 +160,6 @@ private fun TaskListContent( modifier = modifier ) { if (taskList.isEmpty()) { - // Код для отображения пустого списка } else { LazyColumn(modifier = Modifier.padding(all = 10.dp)) { items(items = taskList, key = { it.uid }) { task -> @@ -169,14 +168,14 @@ private fun TaskListContent( if (dismissState.isDismissed(DismissDirection.EndToStart)) { LaunchedEffect(task) { onSwipeLeft(task) - dismissState.reset() // Сбросить состояние после обработки свайпа + dismissState.reset() } } if (dismissState.isDismissed(DismissDirection.StartToEnd)) { LaunchedEffect(task) { onSwipeRight(task) - dismissState.reset() // Сбросить состояние после обработки свайпа + dismissState.reset() } } diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskListViewModel.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskListViewModel.kt index 1511d52..cbab0bb 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskListViewModel.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskListViewModel.kt @@ -17,7 +17,7 @@ class TaskListViewModel( val taskListFavoriteUiState: StateFlow = taskRepository.getAllTasks() .map { tasks -> val filteredTasks = tasks.filter { task -> - task.favorite // Оставить только задачи, у которых favorite = true + task.favorite } val sortedTasks = filteredTasks.sortedByDescending { task -> parseDate(task.endDate)?.timeInMillis ?: Long.MIN_VALUE @@ -26,7 +26,6 @@ class TaskListViewModel( } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState()) - // Для TaskListEndDate (Сортировка по endDate) val taskListEndDateUiState: StateFlow = taskRepository.getAllTasks() .map { tasks -> val sortedTasks = tasks.sortedByDescending { task -> @@ -36,7 +35,6 @@ class TaskListViewModel( } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState()) - // Для TaskList (Сортировка по uid) val taskListUiState: StateFlow = taskRepository.getAllTasks() .map { tasks -> val sortedTasks = tasks.sortedBy { task ->