редактирование билета, подсчет стоимости

This commit is contained in:
Татьяна Артамонова 2023-12-26 00:22:43 +04:00
parent 60c1eb2234
commit 8449a95211
15 changed files with 196 additions and 123 deletions

View File

@ -15,20 +15,6 @@ import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
class MainActivity : ComponentActivity() {
// @RequiresApi(Build.VERSION_CODES.O)
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// setContent {
// AirTicketRentServiceTheme {
// Surface(
// modifier = Modifier.fillMaxSize(),
// color = MaterialTheme.colorScheme.background
// ) {
// MainNavbar()
// }
// }
// }
// }
private val currentUserViewModel: CurrentUserViewModel by viewModels()
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -31,7 +31,7 @@ fun EmailTextField(email: String, onEmailChange: (String) -> Unit) {
shape = RoundedCornerShape(15.dp),
modifier = Modifier.fillMaxWidth(),
label = { Text("Электронная почта") },
placeholder = { Text("Электронная почта") },
placeholder = { Text("example@mail.ru") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
singleLine = true,
)

View File

@ -152,7 +152,8 @@ fun HomeNavGraph(
composable(
route = BottomBarScreen.TicketView.route,
arguments = listOf(
navArgument("id") { type = NavType.IntType }
navArgument("id") { type = NavType.IntType },
navArgument("flightId") { type = NavType.IntType }
)
) {
TicketView(navController)

View File

@ -86,12 +86,12 @@ sealed class BottomBarScreen(
}
}
object TicketView: BottomBarScreen(
route = "ticket-view/{id}",
route = "ticket-view/{id}/{flightId}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id: String): String{
return "ticket-view/$id"
fun passIdAndFlightId(id: String, flightId: String): String{
return "ticket-view/$id/$flightId"
}
}
object FlightInfo: BottomBarScreen(
@ -99,7 +99,7 @@ sealed class BottomBarScreen(
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id:String): String{
fun passId(id: String): String{
return "flight-info/$id"
}
}
@ -117,7 +117,7 @@ sealed class BottomBarScreen(
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id:String): String{
fun passId(id: String): String{
return "ticket-edit/$id"
}
}

View File

@ -1,7 +1,6 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.app.DatePickerDialog
import android.widget.DatePicker
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -13,40 +12,31 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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.FlightDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import java.util.Calendar
import java.util.Date
import ru.ulstu.`is`.airticketrentservice.viewModel.toFlight
@Composable
fun FlightInfo(
navController: NavController,
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketViewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
FlightInfo(
flightUiState = flightViewModel.flightUiState,
onClick = {
val route = BottomBarScreen.TicketView.passId(0.toString())
onClick = { uid: Int ->
Log.d("FlightInfo", "Текущий рейс: $uid")
val route = BottomBarScreen.TicketView.passIdAndFlightId(0.toString(), uid.toString())
navController.navigate(route)
}
)
@ -56,7 +46,7 @@ fun FlightInfo(
@Composable
private fun FlightInfo(
flightUiState: FlightUiState,
onClick: () -> Unit
onClick: (Int) -> Unit
) {
Column(
Modifier
@ -167,7 +157,7 @@ private fun FlightInfo(
readOnly = true
)
Button(
onClick = onClick,
onClick = { onClick(flightUiState.flightDetails.toFlight().id) },
enabled = flightUiState.isEntryValid,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,

View File

@ -230,13 +230,14 @@ private fun FlightListItem(
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = String.format("%s %s", flight.direction_from, flight.direction_to)
text = "${flight.direction_from} --> ${flight.direction_to}"
)
}
}

View File

@ -1,22 +1,34 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
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.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults.TrailingIcon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
@ -25,27 +37,30 @@ 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.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDropDownUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDropDownViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.RentEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightsDropDownListUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.toUiState
@Composable
fun TicketEdit(
navController: NavController,
viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
flightViewModel: FlightDropDownViewModel = 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()) }
flightViewModel.setCurrentDropDownFlight(viewModel.ticketUiState.ticketDetails.flightId)
TicketEdit(
ticketUiState = viewModel.ticketUiState,
flightUiState = flightViewModel.flightUiState,
flightUiState = flightViewModel.flightDropDownUiState,
flightsListUiState = flightViewModel.flightsDropDownListUiState,
onClick = {
coroutineScope.launch {
viewModel.saveTicket()
@ -53,20 +68,80 @@ fun TicketEdit(
}
},
onUpdate = viewModel::updateUiState,
totalCost = totalCost
onFlightUpdate = flightViewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FlightDropDown(
flightUiState: FlightDropDownUiState,
flightsListUiState: FlightsDropDownListUiState,
onFlightUpdate: (Flight) -> Unit
) {
var expanded: Boolean by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
.padding(10.dp),
value = if (flightUiState.flight?.direction_from.isNullOrEmpty() || flightUiState.flight?.direction_to.isNullOrEmpty()) { "Рейс не выбран" }
else { "${flightUiState.flight?.direction_from} --> ${flightUiState.flight?.direction_to}" },
onValueChange = {},
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
trailingIcon = {
TrailingIcon(expanded = expanded)
},
shape = RoundedCornerShape(15.dp),
label = { Text("Рейс") }
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(colorResource(id = R.color.lightGray))
.clip(RoundedCornerShape(15.dp))
.exposedDropdownSize()
) {
flightsListUiState.flightList.forEach { flight ->
DropdownMenuItem(
text = {
Text("${flight.direction_from} --> ${flight.direction_to}")
},
onClick = {
onFlightUpdate(flight)
expanded = false
}
)
}
}
}
}
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TicketEdit(
ticketUiState: TicketUiState,
flightUiState: FlightUiState,
flightUiState: FlightDropDownUiState,
flightsListUiState: FlightsDropDownListUiState,
onClick: () -> Unit,
onUpdate: (TicketDetails) -> Unit,
totalCost: Double
onFlightUpdate: (Flight) -> Unit,
) {
//ticketUiState.ticketDetails.ticket_cost = totalCost
Column(
Modifier
.fillMaxWidth()
@ -75,59 +150,49 @@ private fun TicketEdit(
.padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
FlightDropDown(
flightUiState = flightUiState,
flightsListUiState = flightsListUiState
) {
onUpdate(ticketUiState.ticketDetails.copy(flightId = it.id))
onFlightUpdate(it)
}
val passengersCount by derivedStateOf {
ticketUiState.ticketDetails.passengers_count
}
val oneTicketCost by derivedStateOf {
flightUiState.flight?.one_ticket_cost ?: 0
}
val totalCost by derivedStateOf {
passengersCount * oneTicketCost.toDouble()
}
Log.d("TicketEdit", "Текущий рейс: ${flightUiState.flight}")
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = ticketUiState.ticketDetails.flightId.toString(),
onValueChange = { onUpdate(ticketUiState.ticketDetails.copy(flightId = it.toInt())) },
value = oneTicketCost.toString(),
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Номер рейса") },
label = { Text("Стоимость одного билета") },
singleLine = true
)
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 = ticketUiState.ticketDetails.passengers_count.toString(),
onValueChange = { onUpdate(ticketUiState.ticketDetails.copy(passengers_count = it.toInt())) },
onValueChange = { if (it.isNotEmpty()) {
onUpdate(ticketUiState.ticketDetails.copy(passengers_count = it.toInt()))
} else {
onUpdate(ticketUiState.ticketDetails.copy(passengers_count = 0))
}},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
@ -137,6 +202,19 @@ private fun TicketEdit(
label = { Text("Количество пассажиров") },
singleLine = true
)
// Button(
// onClick = { totalCost = passengersCount * oneTicketCost.toDouble() },
// 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 = "Рассчитать стоимость билета")
// }
TextField(
modifier = Modifier
.fillMaxWidth()

View File

@ -73,7 +73,7 @@ fun TicketList(
floatingActionButton = {
FloatingActionButton(
onClick = {
val route = BottomBarScreen.TicketEdit.passId(0.toString())
val route = BottomBarScreen.TicketEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
},
containerColor = colorResource(R.color.lightlightBlue)
@ -231,13 +231,14 @@ private fun TicketListItem(
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = "Билет ${ticket.id}, Стоимость: ${ticket.ticket_cost}"
text = "Билет ${ticket.id}"
)
}
}

View File

@ -13,10 +13,8 @@ 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
@ -32,17 +30,17 @@ 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
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketViewViewModel
@Composable
fun TicketView(
navController: NavController,
viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
viewModel: TicketViewViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
flightViewModel.setCurrentFlight(viewModel.ticketUiState.ticketDetails.flightId)
//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,

View File

@ -115,7 +115,7 @@ fun Login(navController: NavController, modifier: Modifier = Modifier, loginView
label = {
Text(stringResource(id = R.string.password_label))
},
placeholder = { Text("Пароль") },
placeholder = { Text("password") },
singleLine = true,
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import ru.ulstu.`is`.airticketrentservice.TicketApplication
import ru.ulstu.`is`.airticketrentservice.api.AppService
object AppViewModelProvider {
val Factory = viewModelFactory {
@ -37,6 +36,12 @@ object AppViewModelProvider {
ticketApplication().container.ticketRestRepository
)
}
initializer {
TicketViewViewModel(
this.createSavedStateHandle(),
ticketApplication().container.ticketRestRepository
)
}
initializer {
RentEditViewModel(
this.createSavedStateHandle(),
@ -67,6 +72,11 @@ object AppViewModelProvider {
ticketApplication().container.flightRestRepository
)
}
initializer {
FlightDropDownViewModel(
ticketApplication().container.flightRestRepository
)
}
}
}

View File

@ -16,10 +16,6 @@ class FlightEditViewModel(
savedStateHandle: SavedStateHandle,
private val flightRepository: FlightRepository
) : ViewModel() {
var flightsListUiState by mutableStateOf(FlightsListUiState())
private set
var flightUiState by mutableStateOf(FlightUiState())
private set
@ -28,18 +24,12 @@ class FlightEditViewModel(
init {
viewModelScope.launch {
if (flightUid > 0) {
flightsListUiState = FlightsListUiState(flightRepository.getAllFlights())
flightUiState = flightRepository.getFlightById(flightUid)
.toUiState(true)
}
}
}
fun setCurrentFlight(flightId: Int) {
val flight: Flight? = flightsListUiState.flightList.firstOrNull { flight -> flight.id == flightId }
flight?.let { updateUiState(it.toDetails()) }
}
fun updateUiState(flightDetails: FlightDetails) {
flightUiState = FlightUiState(
flightDetails = flightDetails,
@ -113,6 +103,3 @@ fun Flight.toUiState(isEntryValid: Boolean = false): FlightUiState = FlightUiSta
isEntryValid = isEntryValid
)
data class FlightsListUiState(val flightList: List<Flight> = listOf())

View File

@ -29,20 +29,40 @@ class TicketEditViewModel(
init {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val tickets = ticketRepository.getAllTickets()
val ticket = ticketRepository.getTicketById(ticketUid)
.toUiState(true)
launch(Dispatchers.Main) {
if (ticketUid > 0) {
ticketsListUiState = TicketsListUiState(tickets)
ticketUiState = ticket
}
val tickets = withContext(Dispatchers.IO) {
ticketRepository.getAllTickets()
}
val ticket = if (ticketUid > 0) {
withContext(Dispatchers.IO) {
ticketRepository.getTicketById(ticketUid).toUiState(true)
}
} else {
null
}
ticketsListUiState = TicketsListUiState(tickets)
if (ticket != null) {
ticketUiState = ticket
}
}
}
// init {
// viewModelScope.launch {
// withContext(Dispatchers.IO) {
// val tickets = ticketRepository.getAllTickets()
// val ticket = ticketRepository.getTicketById(ticketUid)
// .toUiState(true)
// launch(Dispatchers.Main) {
// if (ticketUid > 0) {
// ticketsListUiState = TicketsListUiState(tickets)
// ticketUiState = ticket
// }
// }
// }
// }
// }
// init {
// viewModelScope.launch {
// if (ticketUid > 0) {

View File

@ -10,4 +10,5 @@
<color name="lightBlue">#6AABC8</color>
<color name="lightlightBlue">#e7f0f5</color>
<color name="almostwhite">#fcfcff</color>
<color name="lightGray">#f7f2f3</color>
</resources>

View File

@ -8,7 +8,7 @@
},
{
"id": 2,
"passengers_count": 1,
"passengers_count": 3,
"ticket_cost": 2435,
"flightId": 5
}
@ -42,13 +42,13 @@
"one_ticket_cost": 2680
},
{
"id": 7,
"direction_from": "Хабаровск",
"direction_to": "Кострома",
"departure_date": "5-1-2024",
"arrival_date": "5-1-2024",
"tickets_count": 200,
"one_ticket_cost": 6870,
"id": 7
"one_ticket_cost": 6870
},
{
"direction_from": "Астрахань",