This commit is contained in:
Stranni15k 2024-01-08 22:05:09 +04:00
parent c5b54562d3
commit 7ed6a7e9b9
9 changed files with 157 additions and 33 deletions

BIN
.vs/Lection5/v17/.wsuo Normal file

Binary file not shown.

3
.vs/ProjectSettings.json Normal file
View File

@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

View File

@ -0,0 +1,6 @@
{
"ExpandedNodes": [
""
],
"PreviewInSolutionExplorer": false
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

View File

@ -67,6 +67,7 @@ dependencies {
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.2") implementation("androidx.compose.material3:material3:1.1.2")
implementation("com.google.firebase:protolite-well-known-types:18.0.0") implementation("com.google.firebase:protolite-well-known-types:18.0.0")
implementation("com.google.android.material:material:1.10.0")
// Room // Room
val room_version = "2.5.2" val room_version = "2.5.2"

View File

@ -1,14 +1,25 @@
package ru.ulstu.`is`.pmu.ui.task.edit package ru.ulstu.`is`.pmu.ui.task.edit
import android.content.res.Configuration import android.content.res.Configuration
import android.view.ContextThemeWrapper
import android.widget.CalendarView
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column 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.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button 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.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -18,6 +29,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -25,14 +37,19 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp 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.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.android.material.datepicker.MaterialDatePicker
import java.util.Date import java.util.Date
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R 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.database.task.model.Task
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme 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 @Composable
fun TaskEdit( fun TaskEdit(
@ -113,7 +135,6 @@ private fun UserDropDown(
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TaskEdit( private fun TaskEdit(
@ -144,13 +165,33 @@ private fun TaskEdit(
label = { Text(stringResource(id = R.string.task_lastname)) }, label = { Text(stringResource(id = R.string.task_lastname)) },
singleLine = true 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( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = taskUiState.taskDetails.endDate, value = taskUiState.taskDetails.endDate,
onValueChange = { newDate -> onUpdate(taskUiState.taskDetails.copy(endDate = newDate)) }, onValueChange = { newDate -> onUpdate(taskUiState.taskDetails.copy(endDate = newDate)) },
label = { Text(stringResource(id = R.string.task_endDate)) }, label = { Text(stringResource(id = R.string.task_endDate)) },
singleLine = true, singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number) keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
enabled = false
) )
// UserDropDown( // UserDropDown(
// userUiState = userUiState, // userUiState = userUiState,
@ -169,7 +210,6 @@ private fun TaskEdit(
} }
}, },
enabled = taskUiState.isEntryValid, enabled = taskUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text(text = stringResource(R.string.task_save_button)) Text(text = stringResource(R.string.task_save_button))
@ -183,7 +223,7 @@ private fun TaskEdit(
text = { Text("Введите дату по шаблону: 01.12.2023") }, text = { Text("Введите дату по шаблону: 01.12.2023") },
confirmButton = { confirmButton = {
Button(onClick = { showInvalidDateDialog = false }) { Button(onClick = { showInvalidDateDialog = false }) {
Text("ОК") Text("Подтвердить")
} }
} }
) )
@ -195,22 +235,99 @@ fun isValidDate(date: String): Boolean {
return regex.matches(date) 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 @Composable
fun TaskEditPreview() { fun CustomCalendarView(onDateSelected: (Date) -> Unit) {
PmudemoTheme { AndroidView(
Surface( modifier = Modifier.wrapContentSize(),
color = MaterialTheme.colorScheme.background factory = { context ->
) { // Используем стандартный контекст, без применения кастомной темы
TaskEdit( CalendarView(context).apply {
taskUiState = Task.getTask().toUiState(true), // Настройки CalendarView
userUiState = User.DEMO_User.toUiState(), val calendar = Calendar.getInstance()
usersListUiState = UsersListUiState(listOf()),
onClick = {}, setOnDateChangeListener { _, year, month, dayOfMonth ->
onUpdate = {}, calendar.set(Calendar.YEAR, year)
onUserUpdate = {} 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
)
) {
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 = "Подтвердить"
)
}
}
} }
} }
} }

View File

@ -73,7 +73,7 @@ fun TaskList(
} }
} }
) { innerPadding -> ) { innerPadding ->
TaskListContent( // Изменил имя, чтобы избежать путаницы с функцией TaskList TaskListContent(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
.fillMaxSize(), .fillMaxSize(),
@ -82,12 +82,12 @@ fun TaskList(
val route = Screen.TaskEdit.route.replace("{id}", uid.toString()) val route = Screen.TaskEdit.route.replace("{id}", uid.toString())
navController.navigate(route) navController.navigate(route)
}, },
onSwipeLeft = { task: Task -> // Обработка свайпа влево (удаление) onSwipeLeft = { task: Task ->
coroutineScope.launch { coroutineScope.launch {
viewModel.deleteTask(task) viewModel.deleteTask(task)
} }
}, },
onSwipeRight = { task: Task -> // Обработка свайпа вправо (добавить в избранное) onSwipeRight = { task: Task ->
coroutineScope.launch { coroutineScope.launch {
viewModel.favoriteTask(task) viewModel.favoriteTask(task)
} }
@ -102,20 +102,20 @@ private fun SwipeToAction(
dismissState: DismissState, dismissState: DismissState,
task: Task, task: Task,
onClick: (uid: Int) -> Unit, onClick: (uid: Int) -> Unit,
onSwipeRight: (task: Task) -> Unit, // Для добавления в избранное onSwipeRight: (task: Task) -> Unit,
onSwipeLeft: (task: Task) -> Unit // Для удаления onSwipeLeft: (task: Task) -> Unit
) { ) {
SwipeToDismiss( SwipeToDismiss(
state = dismissState, state = dismissState,
directions = setOf( directions = setOf(
DismissDirection.EndToStart, // Для удаления DismissDirection.EndToStart,
DismissDirection.StartToEnd // Для добавления в избранное DismissDirection.StartToEnd
), ),
background = { background = {
val backgroundColor by animateColorAsState( val backgroundColor by animateColorAsState(
when (dismissState.targetValue) { when (dismissState.targetValue) {
DismissedToStart -> Color.Red.copy(alpha = 0.8f) 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 else -> Color.White
}, label = "" }, label = ""
) )
@ -132,7 +132,7 @@ private fun SwipeToAction(
) { ) {
Icon( Icon(
modifier = Modifier.scale(iconScale), 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", contentDescription = if (dismissState.targetValue == DismissedToStart) "Delete" else "Favorite",
tint = Color.White tint = Color.White
) )
@ -160,7 +160,6 @@ private fun TaskListContent(
modifier = modifier modifier = modifier
) { ) {
if (taskList.isEmpty()) { if (taskList.isEmpty()) {
// Код для отображения пустого списка
} else { } else {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) { LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(items = taskList, key = { it.uid }) { task -> items(items = taskList, key = { it.uid }) { task ->
@ -169,14 +168,14 @@ private fun TaskListContent(
if (dismissState.isDismissed(DismissDirection.EndToStart)) { if (dismissState.isDismissed(DismissDirection.EndToStart)) {
LaunchedEffect(task) { LaunchedEffect(task) {
onSwipeLeft(task) onSwipeLeft(task)
dismissState.reset() // Сбросить состояние после обработки свайпа dismissState.reset()
} }
} }
if (dismissState.isDismissed(DismissDirection.StartToEnd)) { if (dismissState.isDismissed(DismissDirection.StartToEnd)) {
LaunchedEffect(task) { LaunchedEffect(task) {
onSwipeRight(task) onSwipeRight(task)
dismissState.reset() // Сбросить состояние после обработки свайпа dismissState.reset()
} }
} }

View File

@ -17,7 +17,7 @@ class TaskListViewModel(
val taskListFavoriteUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks() val taskListFavoriteUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks()
.map { tasks -> .map { tasks ->
val filteredTasks = tasks.filter { task -> val filteredTasks = tasks.filter { task ->
task.favorite // Оставить только задачи, у которых favorite = true task.favorite
} }
val sortedTasks = filteredTasks.sortedByDescending { task -> val sortedTasks = filteredTasks.sortedByDescending { task ->
parseDate(task.endDate)?.timeInMillis ?: Long.MIN_VALUE parseDate(task.endDate)?.timeInMillis ?: Long.MIN_VALUE
@ -26,7 +26,6 @@ class TaskListViewModel(
} }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState())
// Для TaskListEndDate (Сортировка по endDate)
val taskListEndDateUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks() val taskListEndDateUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks()
.map { tasks -> .map { tasks ->
val sortedTasks = tasks.sortedByDescending { task -> val sortedTasks = tasks.sortedByDescending { task ->
@ -36,7 +35,6 @@ class TaskListViewModel(
} }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState())
// Для TaskList (Сортировка по uid)
val taskListUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks() val taskListUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks()
.map { tasks -> .map { tasks ->
val sortedTasks = tasks.sortedBy { task -> val sortedTasks = tasks.sortedBy { task ->