Compare commits

...

2 Commits

22 changed files with 236 additions and 224 deletions

View File

@ -15,20 +15,6 @@ import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
class MainActivity : ComponentActivity() { 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() private val currentUserViewModel: CurrentUserViewModel by viewModels()
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -61,37 +61,21 @@ class RestFlightRepository(
override suspend fun getFlightById(flightId: Int): Flight = override suspend fun getFlightById(flightId: Int): Flight =
service.getFlight(flightId).toFlight() service.getFlight(flightId).toFlight()
// override suspend fun findFlights(
// from: String,
// to: String,
// departureDate: String
// ): List<Flight> {
// val existFlights = dbFlightRepository.getAllFlights().associateBy { it.id }.toMutableMap()
//
// service.findFlights(from, to, departureDate)
// .map { it.toFlight() }
// .forEach { flight ->
// existFlights[flight.id] = flight
// }
// return existFlights.map { it.value }.sortedBy { it.id }
// }
override suspend fun findFlights( override suspend fun findFlights(
from: String, from: String,
to: String, to: String,
departureDate: String departureDate: String
): List<Flight> { ): List<Flight> {
return try { Log.d(RestFlightRepository::class.simpleName, "Find flights")
val foundFlights = service.findFlights(from, to, departureDate) val existFlights = dbFlightRepository.findFlights(from, to, departureDate).associateBy { it.id }.toMutableMap()
if (foundFlights.isNotEmpty()) {
foundFlights.map { it.toFlight() } service.findFlights(from, to, departureDate)
} else { .map { it.toFlight() }
emptyList() .forEach { flight ->
existFlights[flight.id] = flight
} }
} catch (e: Exception) {
e.message?.let { Log.d(RestFlightRepository::class.simpleName, it) } return existFlights.map { it.value }.sortedBy { it.id }
emptyList()
}
} }
override suspend fun getAllFlights(): List<Flight> { override suspend fun getAllFlights(): List<Flight> {
@ -113,28 +97,6 @@ class RestFlightRepository(
return existFlights.map { it.value }.sortedBy { it.id } return existFlights.map { it.value }.sortedBy { it.id }
} }
// flight.direction_from == from ||
// flight.direction_to == to ||
// flight.departure_date == departureDate
// override suspend fun findFlights(
// from: String,
// to: String,
// departureDate: String
// ): List<Flight> {
// Log.d(RestFlightRepository::class.simpleName, "Find flights")
//
//
// val existFlights = dbFlightRepository.findFlights(from, to, departureDate).associateBy { it.id }.toMutableMap()
//
// service.findFlights(from, to, departureDate)
// .map { it.toFlight() }
// .forEach { flight ->
// existFlights[flight.id] = flight
// }
//
// return existFlights.map { it.value }.sortedBy { it.id }
// }
override suspend fun insertFlight(flight: Flight) { override suspend fun insertFlight(flight: Flight) {
service.createFlight(flight.toFlightRemote()).toFlight() service.createFlight(flight.toFlightRemote()).toFlight()

View File

@ -17,7 +17,7 @@ interface FlightDao {
@Query("select * from flights where flights.id = :flightId") @Query("select * from flights where flights.id = :flightId")
fun getFlightById(flightId: Int): Flow<Flight> fun getFlightById(flightId: Int): Flow<Flight>
@Query("select * from flights where flights.direction_from like :from or flights.direction_to like :to or flights.departure_date like :departureDate") @Query("select * from flights where (:from = ' ' or flights.direction_from like :from) and (:to = ' ' or flights.direction_to like :to) and (:departureDate = ' ' or flights.departure_date like :departureDate)")
suspend fun findFlights(from: String, to: String, departureDate: String): List<Flight> suspend fun findFlights(from: String, to: String, departureDate: String): List<Flight>
@Query("SELECT * FROM flights ORDER BY id ASC") @Query("SELECT * FROM flights ORDER BY id ASC")

View File

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

View File

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

View File

@ -7,7 +7,6 @@ import androidx.compose.material.icons.filled.AirplanemodeActive
import androidx.compose.material.icons.filled.FlightTakeoff import androidx.compose.material.icons.filled.FlightTakeoff
import androidx.compose.material.icons.filled.Payments import androidx.compose.material.icons.filled.Payments
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import ru.ulstu.`is`.airticketrentservice.R
sealed class BottomBarScreen( sealed class BottomBarScreen(
val route: String, val route: String,
@ -59,14 +58,15 @@ sealed class BottomBarScreen(
} }
} }
object FoundFlights: BottomBarScreen( object FoundFlights: BottomBarScreen(
route = "found-flights/{direction_from}-{direction_to}-{departure_date}", route = "found-flights/{direction_from}/{direction_to}/{departure_date}",
title ="", title ="",
icon = Icons.Filled.FlightTakeoff icon = Icons.Filled.FlightTakeoff
) { ) {
fun passText(direction_from: String, direction_to: String, departure_date: String): String { fun passText(direction_from: String?, direction_to: String?, departure_date: String?): String {
return "found-flights/$direction_from-$direction_to-$departure_date" return "found-flights/${direction_from?:" "}/${direction_to?:" "}/${departure_date?:" "}"
} }
} }
object UserEdit: BottomBarScreen( object UserEdit: BottomBarScreen(
route = "user-edit/{id}", route = "user-edit/{id}",
title ="", title ="",
@ -86,12 +86,12 @@ sealed class BottomBarScreen(
} }
} }
object TicketView: BottomBarScreen( object TicketView: BottomBarScreen(
route = "ticket-view/{id}", route = "ticket-view/{id}/{flightId}",
title ="", title ="",
icon = Icons.Filled.AccountCircle icon = Icons.Filled.AccountCircle
) { ) {
fun passId(id: String): String{ fun passIdAndFlightId(id: String, flightId: String): String{
return "ticket-view/$id" return "ticket-view/$id/$flightId"
} }
} }
object FlightInfo: BottomBarScreen( object FlightInfo: BottomBarScreen(
@ -99,7 +99,7 @@ sealed class BottomBarScreen(
title ="", title ="",
icon = Icons.Filled.AccountCircle icon = Icons.Filled.AccountCircle
) { ) {
fun passId(id:String): String{ fun passId(id: String): String{
return "flight-info/$id" return "flight-info/$id"
} }
} }
@ -117,7 +117,7 @@ sealed class BottomBarScreen(
title ="", title ="",
icon = Icons.Filled.AccountCircle icon = Icons.Filled.AccountCircle
) { ) {
fun passId(id:String): String{ fun passId(id: String): String{
return "ticket-edit/$id" return "ticket-edit/$id"
} }
} }

View File

@ -51,7 +51,8 @@ fun FlightEdit(
navController.popBackStack() navController.popBackStack()
} }
}, },
onUpdate = viewModel::updateUiState) onUpdate = viewModel::updateUiState
)
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)

View File

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

View File

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

View File

@ -1,35 +1,19 @@
package ru.ulstu.`is`.airticketrentservice.screen package ru.ulstu.`is`.airticketrentservice.screen
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SwipeToDismiss
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberDismissState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -102,7 +86,7 @@ private fun FlightListItem(
modifier = modifier.padding(all = 10.dp) modifier = modifier.padding(all = 10.dp)
) { ) {
Text(text = "${flight.direction_from} --> ${flight.direction_to}") Text(text = "${flight.direction_from} --> ${flight.direction_to}")
Text(text = "${flight.one_ticket_cost}") Text(text = "Цена: ${flight.one_ticket_cost}")
} }
} }
} }

View File

@ -85,9 +85,9 @@ fun MainPage(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
navController: NavController navController: NavController
) { ) {
val from = remember { mutableStateOf("") } val from = remember { mutableStateOf<String?>(null) }
val to = remember { mutableStateOf("") } val to = remember { mutableStateOf<String?>(null) }
val departureDate = remember { mutableStateOf("") } val departureDate = remember { mutableStateOf<String?>(null) }
val state = rememberScrollState() val state = rememberScrollState()
@ -125,7 +125,7 @@ fun MainPage(
TextField(modifier = Modifier TextField(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp),
value = from.value, onValueChange = { from.value = it }, value = from.value?:"", onValueChange = { from.value = it },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f), focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f), unfocusedContainerColor = Color.LightGray.copy(.2f),
@ -141,7 +141,7 @@ fun MainPage(
TextField(modifier = Modifier TextField(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp),
value = to.value, onValueChange = { to.value = it }, value = to.value?:"", onValueChange = { to.value = it },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f), focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f), unfocusedContainerColor = Color.LightGray.copy(.2f),
@ -198,7 +198,7 @@ fun MainPage(
Text("Выбрать дату вылета") Text("Выбрать дату вылета")
} }
Text(text = " ${departureDate.value}", fontSize = 15.sp, color = Color.DarkGray) Text(text = " ${departureDate.value?:""}", fontSize = 15.sp, color = Color.DarkGray)
Button( Button(
modifier = Modifier modifier = Modifier

View File

@ -1,19 +1,34 @@
package ru.ulstu.`is`.airticketrentservice.screen 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.Column
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.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api 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.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable 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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -22,26 +37,30 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R 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.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.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState 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.TicketDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.toUiState
@Composable @Composable
fun TicketEdit( fun TicketEdit(
navController: NavController, navController: NavController,
viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory), viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory) flightViewModel: FlightDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory),
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
flightViewModel.setCurrentFlight(viewModel.ticketUiState.ticketDetails.flightId) flightViewModel.setCurrentDropDownFlight(viewModel.ticketUiState.ticketDetails.flightId)
TicketEdit( TicketEdit(
ticketUiState = viewModel.ticketUiState, ticketUiState = viewModel.ticketUiState,
flightUiState = flightViewModel.flightUiState, flightUiState = flightViewModel.flightDropDownUiState,
flightsListUiState = flightViewModel.flightsDropDownListUiState,
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
viewModel.saveTicket() viewModel.saveTicket()
@ -49,20 +68,80 @@ fun TicketEdit(
} }
}, },
onUpdate = viewModel::updateUiState, onUpdate = viewModel::updateUiState,
totalCost = flightViewModel.flightUiState.flightDetails.one_ticket_cost * viewModel.ticketUiState.ticketDetails.passengers_count.toDouble() 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TicketEdit( private fun TicketEdit(
ticketUiState: TicketUiState, ticketUiState: TicketUiState,
flightUiState: FlightUiState, flightUiState: FlightDropDownUiState,
flightsListUiState: FlightsDropDownListUiState,
onClick: () -> Unit, onClick: () -> Unit,
onUpdate: (TicketDetails) -> Unit, onUpdate: (TicketDetails) -> Unit,
totalCost: Double onFlightUpdate: (Flight) -> Unit,
) { ) {
//ticketUiState.ticketDetails.ticket_cost = totalCost
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@ -71,59 +150,49 @@ private fun TicketEdit(
.padding(vertical = 32.dp), .padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally 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( TextField(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(10.dp), .padding(10.dp),
value = ticketUiState.ticketDetails.flightId.toString(), value = oneTicketCost.toString(),
onValueChange = { onUpdate(ticketUiState.ticketDetails.copy(flightId = it.toInt())) }, onValueChange = { },
colors = TextFieldDefaults.textFieldColors( colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f), containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent focusedIndicatorColor = Color.Transparent
), ),
shape = RoundedCornerShape(15.dp), shape = RoundedCornerShape(15.dp),
label = { Text("Номер рейса") }, label = { Text("Стоимость одного билета") },
singleLine = true 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( TextField(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(10.dp), .padding(10.dp),
value = ticketUiState.ticketDetails.passengers_count.toString(), 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( colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f), containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
@ -133,6 +202,19 @@ private fun TicketEdit(
label = { Text("Количество пассажиров") }, label = { Text("Количество пассажиров") },
singleLine = true 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( TextField(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View File

@ -73,7 +73,7 @@ fun TicketList(
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
val route = BottomBarScreen.TicketEdit.passId(0.toString()) val route = BottomBarScreen.TicketEdit.route.replace("{id}", 0.toString())
navController.navigate(route) navController.navigate(route)
}, },
containerColor = colorResource(R.color.lightlightBlue) containerColor = colorResource(R.color.lightlightBlue)
@ -231,13 +231,14 @@ private fun TicketListItem(
) { ) {
Card( Card(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) { ) {
Column( Column(
modifier = modifier.padding(all = 10.dp) modifier = modifier.padding(all = 10.dp)
) { ) {
Text( 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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf import androidx.compose.runtime.mutableDoubleStateOf
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.ui.Alignment 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
@ -32,17 +30,17 @@ import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketDetails 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.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketViewViewModel
@Composable @Composable
fun TicketView( fun TicketView(
navController: NavController, navController: NavController,
viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory), viewModel: TicketViewViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory) flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) { ) {
val coroutineScope = rememberCoroutineScope() 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()) } val totalCost by remember { mutableDoubleStateOf(flightViewModel.flightUiState.flightDetails.one_ticket_cost * viewModel.ticketUiState.ticketDetails.passengers_count.toDouble()) }
TicketEdit( TicketEdit(
ticketUiState = viewModel.ticketUiState, ticketUiState = viewModel.ticketUiState,

View File

@ -115,7 +115,7 @@ fun Login(navController: NavController, modifier: Modifier = Modifier, loginView
label = { label = {
Text(stringResource(id = R.string.password_label)) Text(stringResource(id = R.string.password_label))
}, },
placeholder = { Text("Пароль") }, placeholder = { Text("password") },
singleLine = true, singleLine = true,
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), 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.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
import ru.ulstu.`is`.airticketrentservice.TicketApplication import ru.ulstu.`is`.airticketrentservice.TicketApplication
import ru.ulstu.`is`.airticketrentservice.api.AppService
object AppViewModelProvider { object AppViewModelProvider {
val Factory = viewModelFactory { val Factory = viewModelFactory {
@ -37,6 +36,12 @@ object AppViewModelProvider {
ticketApplication().container.ticketRestRepository ticketApplication().container.ticketRestRepository
) )
} }
initializer {
TicketViewViewModel(
this.createSavedStateHandle(),
ticketApplication().container.ticketRestRepository
)
}
initializer { initializer {
RentEditViewModel( RentEditViewModel(
this.createSavedStateHandle(), this.createSavedStateHandle(),
@ -67,6 +72,11 @@ object AppViewModelProvider {
ticketApplication().container.flightRestRepository ticketApplication().container.flightRestRepository
) )
} }
initializer {
FlightDropDownViewModel(
ticketApplication().container.flightRestRepository
)
}
} }
} }

View File

@ -7,15 +7,8 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.model.toFlight
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.models.Flight import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository
@ -41,9 +34,3 @@ class FindFlightsViewModel(
} }
data class FoundFlightsUiState(val flightList: List<Flight> = listOf()) data class FoundFlightsUiState(val flightList: List<Flight> = listOf())
// var foundFlightsList: List<Flight> by mutableStateOf(emptyList())
// private set
//foundFlightsList = appService.foundFlights().map { it.toFlight() }

View File

@ -16,10 +16,6 @@ class FlightEditViewModel(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
private val flightRepository: FlightRepository private val flightRepository: FlightRepository
) : ViewModel() { ) : ViewModel() {
var flightsListUiState by mutableStateOf(FlightsListUiState())
private set
var flightUiState by mutableStateOf(FlightUiState()) var flightUiState by mutableStateOf(FlightUiState())
private set private set
@ -28,18 +24,12 @@ class FlightEditViewModel(
init { init {
viewModelScope.launch { viewModelScope.launch {
if (flightUid > 0) { if (flightUid > 0) {
flightsListUiState = FlightsListUiState(flightRepository.getAllFlights())
flightUiState = flightRepository.getFlightById(flightUid) flightUiState = flightRepository.getFlightById(flightUid)
.toUiState(true) .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) { fun updateUiState(flightDetails: FlightDetails) {
flightUiState = FlightUiState( flightUiState = FlightUiState(
flightDetails = flightDetails, flightDetails = flightDetails,
@ -113,6 +103,3 @@ fun Flight.toUiState(isEntryValid: Boolean = false): FlightUiState = FlightUiSta
isEntryValid = isEntryValid isEntryValid = isEntryValid
) )
data class FlightsListUiState(val flightList: List<Flight> = listOf())

View File

@ -1,5 +1,6 @@
package ru.ulstu.`is`.airticketrentservice.viewModel package ru.ulstu.`is`.airticketrentservice.viewModel
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -25,22 +26,43 @@ class TicketEditViewModel(
private val ticketUid: Int = checkNotNull(savedStateHandle["id"]) private val ticketUid: Int = checkNotNull(savedStateHandle["id"])
init { init {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { val tickets = withContext(Dispatchers.IO) {
val tickets = ticketRepository.getAllTickets() ticketRepository.getAllTickets()
val ticket = ticketRepository.getTicketById(ticketUid) }
.toUiState(true) val ticket = if (ticketUid > 0) {
launch(Dispatchers.Main) { withContext(Dispatchers.IO) {
if (ticketUid > 0) { ticketRepository.getTicketById(ticketUid).toUiState(true)
ticketsListUiState = TicketsListUiState(tickets)
ticketUiState = ticket
}
} }
} 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 { // init {
// viewModelScope.launch { // viewModelScope.launch {
// if (ticketUid > 0) { // if (ticketUid > 0) {

View File

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

View File

@ -8,7 +8,7 @@
}, },
{ {
"id": 2, "id": 2,
"passengers_count": 1, "passengers_count": 3,
"ticket_cost": 2435, "ticket_cost": 2435,
"flightId": 5 "flightId": 5
} }
@ -35,20 +35,20 @@
{ {
"id": 6, "id": 6,
"direction_from": "Ульяновск", "direction_from": "Ульяновск",
"direction_to": "Москва ", "direction_to": "Москва",
"departure_date": "26-12-2023", "departure_date": "26-12-2023",
"arrival_date": "26-12-2023", "arrival_date": "26-12-2023",
"tickets_count": 50, "tickets_count": 50,
"one_ticket_cost": 2680 "one_ticket_cost": 2680
}, },
{ {
"id": 7,
"direction_from": "Хабаровск", "direction_from": "Хабаровск",
"direction_to": "Кострома ", "direction_to": "Кострома",
"departure_date": "5-1-2024", "departure_date": "5-1-2024",
"arrival_date": "5-1-2024", "arrival_date": "5-1-2024",
"tickets_count": 200, "tickets_count": 200,
"one_ticket_cost": 6870, "one_ticket_cost": 6870
"id": 7
}, },
{ {
"direction_from": "Астрахань", "direction_from": "Астрахань",
@ -83,7 +83,7 @@
"id": 2, "id": 2,
"surname": "Артамонова", "surname": "Артамонова",
"name": "Татьяна", "name": "Татьяна",
"patronymic": "Валерьевн", "patronymic": "Валерьевна",
"date_of_birth": "7-11-2003", "date_of_birth": "7-11-2003",
"email": "usertt@mail.ru", "email": "usertt@mail.ru",
"password": "usertt", "password": "usertt",

View File

@ -35,7 +35,7 @@
{ {
"id": 6, "id": 6,
"direction_from": "Ульяновск", "direction_from": "Ульяновск",
"direction_to": "Москва ", "direction_to": "Москва",
"departure_date": "26-12-2023", "departure_date": "26-12-2023",
"arrival_date": "26-12-2023", "arrival_date": "26-12-2023",
"tickets_count": 50, "tickets_count": 50,
@ -43,7 +43,7 @@
}, },
{ {
"direction_from": "Хабаровск", "direction_from": "Хабаровск",
"direction_to": "Кострома ", "direction_to": "Кострома",
"departure_date": "5-1-2024", "departure_date": "5-1-2024",
"arrival_date": "5-1-2024", "arrival_date": "5-1-2024",
"tickets_count": 200, "tickets_count": 200,
@ -128,7 +128,7 @@
"password": "password", "password": "password",
"role": "user", "role": "user",
"id": 6 "id": 6
}, },
{ {
"surname": "ккк", "surname": "ккк",
"name": "ккк", "name": "ккк",
@ -141,17 +141,17 @@
} }
], ],
"rents": [ "rents": [
{ {
"id": 1, "id": 1,
"status": "Ожидает подтверждения", "status": "Ожидает подтверждения",
"userId": 2, "userId": 2,
"ticketId": 1 "ticketId": 1
}, },
{ {
"id": 2, "id": 2,
"status": "Подтверждено", "status": "Подтверждено",
"userId": 2, "userId": 2,
"ticketId": 2 "ticketId": 2
} }
] ]
} }