diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketList.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketList.kt new file mode 100644 index 0000000..f03b2f1 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketList.kt @@ -0,0 +1,244 @@ +package ru.ulstu.`is`.airticketrentservice.screen + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeOut +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DismissDirection +import androidx.compose.material3.DismissState +import androidx.compose.material3.DismissValue +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SwipeToDismiss +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDismissState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +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.colorResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import ru.ulstu.`is`.airticketrentservice.R +import ru.ulstu.`is`.airticketrentservice.database.models.Ticket +import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen +import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider +import ru.ulstu.`is`.airticketrentservice.viewModel.FlightListViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel + +@Composable +fun TicketList( + navController: NavController, + flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory), + viewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val ticketListUiState = viewModel.ticketListUiState.collectAsLazyPagingItems() + Scaffold( + topBar = {}, + floatingActionButton = { + FloatingActionButton( + onClick = { + val route = BottomBarScreen.TicketEdit.passId(0.toString()) + navController.navigate(route) + }, + containerColor = colorResource(R.color.lightlightBlue) + ) { + Icon(Icons.Filled.Add, "Добавить") + } + } + ) { innerPadding -> + TicketList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + ticketList = ticketListUiState, + onClick = { id: Int -> + val route = BottomBarScreen.TicketEdit.passId(id.toString()) + navController.navigate(route) + }, + onSwipe = { ticket: Ticket -> + coroutineScope.launch { + viewModel.deleteTicket(ticket) + } + } + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun DismissBackground(dismissState: DismissState) { + val color = when (dismissState.dismissDirection) { + DismissDirection.StartToEnd -> Color.Transparent + DismissDirection.EndToStart -> Color(0xFFFF1744) + null -> Color.Transparent + } + val direction = dismissState.dismissDirection + + Row( + modifier = Modifier + .fillMaxSize() + .background(color) + .padding(12.dp, 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + if (direction == DismissDirection.EndToStart) { + Icon( + Icons.Default.Delete, + contentDescription = "delete", + tint = Color.White + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SwipeToDelete( + dismissState: DismissState, + ticket: Ticket, + onClick: (id: Int) -> Unit +) { + SwipeToDismiss( + modifier = Modifier.zIndex(1f), + state = dismissState, + directions = setOf( + DismissDirection.EndToStart + ), + background = { + DismissBackground(dismissState) + }, + dismissContent = { + TicketListItem(ticket = ticket, + modifier = Modifier + .padding(vertical = 7.dp) + .clickable { onClick(ticket.id) }) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@Composable +private fun TicketList( + modifier: Modifier = Modifier, + ticketList: LazyPagingItems, + onClick: (id: Int) -> Unit, + onSwipe: (ticket: Ticket) -> Unit +) { + val refreshScope = rememberCoroutineScope() + var refreshing by remember { mutableStateOf(false) } + fun refresh() = refreshScope.launch { + refreshing = true + ticketList.refresh() + refreshing = false + } + + val state = rememberPullRefreshState(refreshing, ::refresh) + Box( + modifier = modifier.pullRefresh(state) + ) { + Column( + modifier = modifier.fillMaxSize() + ) { + LazyColumn(modifier = Modifier.padding(all = 10.dp)) { + items( + count = ticketList.itemCount, + key = ticketList.itemKey(), + contentType = ticketList.itemContentType() + ) { index -> + val ticket = ticketList[index] + ticket?.let { + var show by remember { mutableStateOf(true) } + val dismissState = rememberDismissState( + confirmValueChange = { + if (it == DismissValue.DismissedToStart || + it == DismissValue.DismissedToEnd + ) { + show = false + true + } else false + }, positionalThreshold = { 200.dp.toPx() } + ) + + AnimatedVisibility( + show, exit = fadeOut(spring()) + ) { + SwipeToDelete( + dismissState = dismissState, + ticket = ticket, + onClick = onClick + ) + } + + LaunchedEffect(show) { + if (!show) { + delay(800) + onSwipe(ticket) + } + } + } + } + } + PullRefreshIndicator( + refreshing, state, + Modifier + .align(Alignment.CenterHorizontally) + .zIndex(100f) + ) + } + } +} + +@Composable +private fun TicketListItem( + ticket: Ticket, modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + Text( + text = "Билет ${ticket.id}, Стоимость: ${ticket.ticket_cost}" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketView.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketView.kt new file mode 100644 index 0000000..a51980c --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketView.kt @@ -0,0 +1,204 @@ +package ru.ulstu.`is`.airticketrentservice.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableDoubleStateOf +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.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import kotlinx.coroutines.launch +import ru.ulstu.`is`.airticketrentservice.R +import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen +import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider +import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketDetails +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState + +@Composable +fun TicketView( + navController: NavController, + viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory), + flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + flightViewModel.setCurrentFlight(viewModel.ticketUiState.ticketDetails.flightId) + val totalCost by remember { mutableDoubleStateOf(flightViewModel.flightUiState.flightDetails.one_ticket_cost * viewModel.ticketUiState.ticketDetails.passengers_count.toDouble()) } + TicketEdit( + ticketUiState = viewModel.ticketUiState, + flightUiState = flightViewModel.flightUiState, + onClick = { + coroutineScope.launch { + viewModel.saveTicket() + navController.navigate(BottomBarScreen.RentEdit.passId(0.toString())) + } + }, + onUpdate = viewModel::updateUiState, + totalCost = totalCost + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TicketEdit( + ticketUiState: TicketUiState, + flightUiState: FlightUiState, + onClick: () -> Unit, + onUpdate: (TicketDetails) -> Unit, + totalCost: Double +) { + //ticketUiState.ticketDetails.ticket_cost = totalCost + Column( + Modifier + .fillMaxWidth() + .padding(all = 10.dp) + .padding(horizontal = 24.dp) + .padding(vertical = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = flightUiState.flightDetails.direction_from, + onValueChange = {}, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text(stringResource(id = R.string.ticket_from)) }, + singleLine = true, + readOnly = true + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = flightUiState.flightDetails.direction_to, + onValueChange = {}, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text(stringResource(id = R.string.ticket_to)) }, + singleLine = true, + readOnly = true + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = flightUiState.flightDetails.departure_date, + onValueChange = {}, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text(stringResource(id = R.string.ticket_arrivalDate)) }, + singleLine = true, + readOnly = true + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = flightUiState.flightDetails.arrival_date, + onValueChange = {}, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text(stringResource(id = R.string.ticket_departureDate)) }, + singleLine = true, + readOnly = true + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = totalCost.toString(), + onValueChange = { onUpdate(ticketUiState.ticketDetails.copy(ticket_cost = it.toDouble())) }, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text("Стоимость билета") }, + singleLine = true, + readOnly = true + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = ticketUiState.ticketDetails.passengers_count.toString(), + onValueChange = { onUpdate(ticketUiState.ticketDetails.copy(passengers_count = it.toInt())) }, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text("Количество пассажиров") }, + singleLine = true + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = totalCost.toString(), + onValueChange = {}, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.LightGray.copy(.2f), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + label = { Text("Стоимость за всех пассажиров") }, + singleLine = true, + readOnly = true + ) + Button( + onClick = onClick, + enabled = ticketUiState.isEntryValid, + elevation = ButtonDefaults.buttonElevation( + defaultElevation = 10.dp, + pressedElevation = 6.dp + ), + shape = RoundedCornerShape(15.dp), + colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)), + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "Забронировать") + } + } +} \ No newline at end of file