exception handling

This commit is contained in:
dasha 2023-12-18 01:43:48 +04:00
parent 42c21b0ce7
commit c0b94141a3
23 changed files with 493 additions and 75189 deletions

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="KFRSEQ6DTWWWQOE6" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-12-17T14:14:18.887820Z" />
</component>
</project>

View File

@ -1,5 +1,6 @@
package com.example.myapplication
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -20,6 +21,7 @@ class MainComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
application.deleteDatabase("pmy-db")
appContext = applicationContext
setContent {
PmudemoTheme(darkTheme = isDarkTheme.value) {
LaunchedEffect(key1 = true) {
@ -40,4 +42,7 @@ class MainComposeActivity : ComponentActivity() {
}
}
}
companion object {
lateinit var appContext: Context
}
}

View File

@ -15,21 +15,17 @@ fun Authenticator(
viewModel: AuthenticatorViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val scope = rememberCoroutineScope()
val login = dataStoreManager.getLogin().collectAsState(initial = "").value
LiveStore.user.value = viewModel.authUiState.user
fun synchronize() {
scope.launch {
if (login == "") {
LiveStore.user.value = null
return@launch
}
val overlap = viewModel.findUserByLogin(login)
if (overlap == null) {
dataStoreManager.setLogin("")
return@launch
}
LiveStore.user.value = overlap
viewModel.findUserByLogin(login)
}
}

View File

@ -1,18 +1,27 @@
package com.example.myapplication.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.repository.UserRepository
class AuthenticatorViewModel(
private val userRepository: UserRepository
) : MyViewModel() {
suspend fun findUserByLogin(login: String): User? {
var user: User? = null
var authUiState by mutableStateOf(AuthenticatorUiState())
private set
suspend fun findUserByLogin(login: String) {
runInScope(
actionSuccess = {
user = userRepository.getUser(login)
authUiState = AuthenticatorUiState(userRepository.getUser(login))
},
actionError = {
authUiState = AuthenticatorUiState()
}
)
return user
}
}
}
data class AuthenticatorUiState(val user: User? = null)

View File

@ -60,7 +60,6 @@ fun Cart(
LaunchedEffect(Unit) {
viewModel.refreshState()
}
Cart(
cartUiState = cartUiState,
modifier = Modifier

View File

@ -3,7 +3,6 @@ package com.example.myapplication.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.myapplication.LiveStore
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
@ -21,15 +20,21 @@ class CartViewModel(
private val orderRepository: OrderRepository,
private val orderSessionRepository: OrderSessionRepository,
private val userRepository: UserRepository,
) : ViewModel() {
) : MyViewModel() {
private var isLoading: Boolean = false
var cartUiState by mutableStateOf(CartUiState())
private set
suspend fun refreshState() {
val userId: Int = LiveStore.user.value?.uid ?: 0
val cart = userRepository.getCartByUser(userId)
cartUiState = CartUiState(cart)
runInScope(
actionSuccess = {
val cart = userRepository.getCartByUser(userId)
cartUiState = CartUiState(cart)
}, actionError = {
cartUiState = CartUiState()
}
)
}
suspend fun addToOrder(sessions: List<SessionFromCart>) {
@ -39,38 +44,61 @@ class CartViewModel(
val userId: Int = LiveStore.user.value?.uid ?: return
if (sessions.isEmpty())
return
val orderId = orderRepository.insertOrder(Order(0, userId, LocalDateTime.now()))
sessions.forEach { session ->
orderSessionRepository.insertOrderSession(
OrderSessionCrossRef(
orderId.toInt(),
session.uid,
session.price,
session.count
)
)
}
userSessionRepository.deleteUserSessions(userId)
refreshState()
runInScope(
actionSuccess = {
val orderId = orderRepository.insertOrder(Order(0, userId, LocalDateTime.now()))
sessions.forEach { session ->
orderSessionRepository.insertOrderSession(
OrderSessionCrossRef(
orderId.toInt(),
session.uid,
session.price,
session.count
)
)
}
userSessionRepository.deleteUserSessions(userId)
refreshState()
}
)
}
suspend fun removeFromCart(session: Session, count: Int = 0) {
val userId: Int = LiveStore.user.value?.uid ?: return
userSessionRepository.deleteUserSession(UserSessionCrossRef(userId, session.uid, count))
refreshState()
runInScope(
actionSuccess = {
userSessionRepository.deleteUserSession(
UserSessionCrossRef(
userId,
session.uid,
count
)
)
refreshState()
}
)
}
suspend fun updateFromCart(session: Session, count: Int, availableCount: Int): Boolean {
val userId: Int = LiveStore.user.value?.uid ?: return false
suspend fun updateFromCart(session: Session, count: Int, availableCount: Int) {
val userId: Int = LiveStore.user.value?.uid ?: return
if (count == 0) {
removeFromCart(session, count)
return false
return
}
if (count > availableCount)
return false
userSessionRepository.updateUserSession(UserSessionCrossRef(userId, session.uid, count))
refreshState()
return true
return
runInScope(
actionSuccess = {
userSessionRepository.updateUserSession(
UserSessionCrossRef(
userId,
session.uid,
count
)
)
refreshState()
}
)
}
}

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.api.session.ReportRemote
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import kotlinx.coroutines.launch
@ -39,57 +40,68 @@ fun Report(
val dateStateStart = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val dateStateEnd = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val coroutineScope = rememberCoroutineScope()
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(all = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(
text = "Начало периода",
style = MaterialTheme.typography.headlineLarge
)
DatePicker(state = dateStateStart)
val selectedDateStart = dateStateStart.selectedDateMillis
if (selectedDateStart != null) {
viewModel.onUpdate(
viewModel.reportUiState.reportDetails.copy(
startDate =
Date(selectedDateStart)
)
when (viewModel.apiStatus) {
ApiStatus.DONE -> {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(all = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
} else {
viewModel.onUpdate(viewModel.reportUiState.reportDetails.copy(startDate = Date(0)))
}
Text(
text = "Конец периода",
style = MaterialTheme.typography.headlineLarge
)
DatePicker(state = dateStateEnd)
val selectedDateEnd = dateStateEnd.selectedDateMillis
if (selectedDateEnd != null) {
viewModel.onUpdate(
viewModel.reportUiState.reportDetails.copy(
endDate =
Date(selectedDateEnd)
{
Text(
text = "Начало периода",
style = MaterialTheme.typography.headlineLarge
)
)
} else {
viewModel.onUpdate(viewModel.reportUiState.reportDetails.copy(endDate = Date(0)))
DatePicker(state = dateStateStart)
val selectedDateStart = dateStateStart.selectedDateMillis
if (selectedDateStart != null) {
viewModel.onUpdate(
viewModel.reportUiState.reportDetails.copy(
startDate =
Date(selectedDateStart)
)
)
} else {
viewModel.onUpdate(viewModel.reportUiState.reportDetails.copy(startDate = Date(0)))
}
Text(
text = "Конец периода",
style = MaterialTheme.typography.headlineLarge
)
DatePicker(state = dateStateEnd)
val selectedDateEnd = dateStateEnd.selectedDateMillis
if (selectedDateEnd != null) {
viewModel.onUpdate(
viewModel.reportUiState.reportDetails.copy(
endDate =
Date(selectedDateEnd)
)
)
} else {
viewModel.onUpdate(viewModel.reportUiState.reportDetails.copy(endDate = Date(0)))
}
Button(
onClick = { coroutineScope.launch { viewModel.getReport() } },
enabled = viewModel.reportUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Получить отчет")
}
Spacer(modifier = Modifier.height(16.dp))
CardScreen(reportData = viewModel.reportResultUiState.report)
}
}
Button(
onClick = { coroutineScope.launch { viewModel.getReport() } },
enabled = viewModel.reportUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Получить отчет")
}
Spacer(modifier = Modifier.height(16.dp))
CardScreen(reportData = viewModel.reportResultUiState.report)
ApiStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { }
)
}
}

View File

@ -3,12 +3,11 @@ package com.example.myapplication.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.myapplication.api.session.ReportRemote
import com.example.myapplication.api.session.RestSessionRepository
import java.util.Date
class ReportViewModel(private val serialRepository: RestSessionRepository) : ViewModel() {
class ReportViewModel(private val serialRepository: RestSessionRepository) : MyViewModel() {
var reportUiState by mutableStateOf(ReportUiState())
private set
@ -32,11 +31,17 @@ class ReportViewModel(private val serialRepository: RestSessionRepository) : Vie
suspend fun getReport() {
if (validateInput()) {
val temp = serialRepository.getReport(
reportUiState.reportDetails.startDate,
reportUiState.reportDetails.endDate
runInScope(
actionSuccess = {
val temp = serialRepository.getReport(
reportUiState.reportDetails.startDate,
reportUiState.reportDetails.endDate
)
reportResultUiState = ReportResultUiState(temp)
}, actionError = {
reportResultUiState = ReportResultUiState()
}
)
reportResultUiState = ReportResultUiState(temp)
}
}
}

View File

@ -93,22 +93,21 @@ fun Topbar(
Spacer(modifier = Modifier.width(16.dp))
if (currentScreen?.route == Screen.CinemaList.route) {
Search(
initValue = LiveStore.searchRequest.value ?: "",
onDone = {
LiveStore.searchRequest.value = it
},
modifier = Modifier
.weight(1f)
.height(36.dp)
.background(
color = MaterialTheme.colorScheme.onPrimary,
RoundedCornerShape(18.dp)
)
.padding(start = 13.dp, top = 8.dp)
)
}
Search(
initValue = LiveStore.searchRequest.value ?: "",
onDone = {
navController.navigate(Screen.CinemaList.route)
LiveStore.searchRequest.value = it
},
modifier = Modifier
.weight(1f)
.height(36.dp)
.background(
color = MaterialTheme.colorScheme.onPrimary,
RoundedCornerShape(18.dp)
)
.padding(start = 13.dp, top = 8.dp)
)
}
}
}
@ -188,7 +187,7 @@ fun Navhost(
Screen.OrderView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { OrderView(it.getInt("id")) }
backStackEntry.arguments?.let { OrderView(navController) }
}
composable(Screen.Report.route) { Report() }
}

View File

@ -56,7 +56,6 @@ fun CinemaList(
LaunchedEffect(searchPattern.value) {
viewModel.refresh()
}
Scaffold(
topBar = {},
floatingActionButton = {
@ -99,6 +98,7 @@ fun CinemaList(
}
}
@Composable
private fun CinemaList(
modifier: Modifier = Modifier,

View File

@ -21,12 +21,16 @@ class CinemaListViewModel(
fun refresh() {
val name = "%${LiveStore.searchRequest.value}%"
val pagingSource = cinemaRepository.getAllCinemas(name)
cinemaPagingFlow = CinemaPagingFlowState(pagingSource.cachedIn(viewModelScope))
runInScope(actionSuccess = {
val pagingSource = cinemaRepository.getAllCinemas(name)
cinemaPagingFlow = CinemaPagingFlowState(pagingSource.cachedIn(viewModelScope))
})
}
suspend fun deleteCinema(cinema: Cinema) {
cinemaRepository.deleteCinema(cinema)
runInScope(actionSuccess = {
cinemaRepository.deleteCinema(cinema)
})
}
}

View File

@ -31,6 +31,9 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.LiveStore
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.composeui.ErrorPlaceholder
import com.example.myapplication.composeui.LoadingPlaceholder
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.UserRole
@ -42,104 +45,112 @@ fun CinemaView(
) {
val cinemaUiState = viewModel.cinemaUiState
val user = LiveStore.user.observeAsState()
LaunchedEffect(Unit) {
viewModel.refreshState()
}
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
) {
val cinema: Cinema? = cinemaUiState.cinemaWithSessions?.cinema
if (cinema != null) {
Box(
when (viewModel.apiStatus) {
ApiStatus.DONE -> {
Column(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)
.padding(16.dp)
.fillMaxSize(),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(color = MaterialTheme.colorScheme.secondary),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
val cinema: Cinema? = cinemaUiState.cinemaWithSessions?.cinema
if (cinema != null) {
Box(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)
) {
Text(
text = "${cinema.name}, ${cinema.year}",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onSecondary
),
modifier = Modifier
.padding(bottom = 8.dp)
)
}
if (cinema.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
cinema.image,
0,
cinema.image.size
).asImageBitmap(),
contentDescription = null,
Column(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(4.dp)
)
.padding(16.dp)
.background(color = MaterialTheme.colorScheme.secondary),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "${cinema.name}, ${cinema.year}",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onSecondary
),
modifier = Modifier
.padding(bottom = 8.dp)
)
}
Text(
text = cinema.description,
color = MaterialTheme.colorScheme.onSecondary
)
}
}
}
if (cinema.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
cinema.image,
0,
cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(4.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Сеансы",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onBackground
),
modifier = Modifier
.weight(1f) // Занимает доступное пространство
.padding(top = 8.dp, bottom = 8.dp)
)
if (user.value?.role == UserRole.ADMIN) {
IconButton(
onClick = {
val route = Screen.SessionEdit.route.replace("{id}", 0.toString())
.replace(
"{cinemaId}",
cinemaUiState.cinemaWithSessions?.cinema?.uid.toString()
Text(
text = cinema.description,
color = MaterialTheme.colorScheme.onSecondary
)
navController.navigate(route)
}
}
}
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Добавить сеанс",
Text(
text = "Сеансы",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onBackground
),
modifier = Modifier
.weight(1f) // Занимает доступное пространство
.padding(top = 8.dp, bottom = 8.dp)
)
if (user.value?.role == UserRole.ADMIN) {
IconButton(
onClick = {
val route = Screen.SessionEdit.route.replace("{id}", 0.toString())
.replace(
"{cinemaId}",
cinemaUiState.cinemaWithSessions?.cinema?.uid.toString()
)
navController.navigate(route)
}
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Добавить сеанс",
)
}
}
}
if (cinemaUiState.cinemaWithSessions != null) {
SessionList(viewModel, navController)
}
}
}
if (cinemaUiState.cinemaWithSessions != null) {
SessionList(viewModel, navController)
}
ApiStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.popBackStack() }
)
}
}

View File

@ -4,13 +4,13 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.example.myapplication.composeui.MyViewModel
import com.example.myapplication.database.entities.model.CinemaWithSessions
import com.example.myapplication.database.entities.repository.CinemaRepository
class CinemaViewModel(
savedStateHandle: SavedStateHandle, private val cinemaRepository: CinemaRepository
) : ViewModel() {
) : MyViewModel() {
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
var cinemaUiState by mutableStateOf(CinemaUiState())
@ -18,7 +18,11 @@ class CinemaViewModel(
suspend fun refreshState() {
if (cinemaUid > 0) {
cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
runInScope(actionSuccess = {
cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
}, actionError = {
cinemaUiState = CinemaUiState()
})
}
}
}

View File

@ -1,7 +1,7 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.example.myapplication.composeui.MyViewModel
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.repository.OrderRepository
import kotlinx.coroutines.flow.Flow
@ -9,10 +9,12 @@ import kotlinx.coroutines.flow.emptyFlow
class OrderListViewModel(
private val orderRepository: OrderRepository
) : ViewModel() {
) : MyViewModel() {
var orderListUiState: Flow<PagingData<Order>> = emptyFlow()
fun refreshState(userId: Int = 0) {
orderListUiState = orderRepository.getAllOrders(userId)
runInScope(actionSuccess = {
orderListUiState = orderRepository.getAllOrders(userId)
})
}
}

View File

@ -1,6 +1,5 @@
package com.example.myapplication.database.entities.composeui
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -15,100 +14,98 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.ui.theme.PmudemoTheme
import androidx.navigation.NavController
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.composeui.ErrorPlaceholder
import com.example.myapplication.composeui.LoadingPlaceholder
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun OrderView(
id: Int,
navController: NavController,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val orderUiState by viewModel.orderUiState.collectAsState()
LazyColumn(
modifier = Modifier
.padding(10.dp)
) {
items(orderUiState.sessionList) { session ->
val count = remember { mutableStateOf(session.count) }
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formattedDate = dateFormatter.format(session.dateTime)
val orderUiState = viewModel.orderUiState
Text(
text = formattedDate,
color = MaterialTheme.colorScheme.onBackground,
)
Box(
LaunchedEffect(Unit) {
viewModel.refreshState()
}
when (viewModel.apiStatus) {
ApiStatus.DONE -> {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondary)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (session.cinema.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
session.cinema.image,
0,
session.cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
items(orderUiState.sessionList) { session ->
val count = remember { mutableIntStateOf(session.count) }
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formattedDate = dateFormatter.format(session.dateTime)
Column(
Text(
text = formattedDate,
color = MaterialTheme.colorScheme.onBackground,
)
Box(
modifier = Modifier
.weight(1f)
.padding(start = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondary)
) {
Text(
text = "${session.cinema.name}, ${session.cinema.year}\n" +
"Цена: ${session.frozenPrice}\n" +
"Количество: ${count.value}",
color = MaterialTheme.colorScheme.onSecondary
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (session.cinema.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
session.cinema.image,
0,
session.cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "${session.cinema.name}, ${session.cinema.year}\n" +
"Цена: ${session.frozenPrice}\n" +
"Количество: $count",
color = MaterialTheme.colorScheme.onSecondary
)
}
}
}
}
}
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun OrderViewPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
OrderView(id = 1)
}
ApiStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.popBackStack() }
)
}
}

View File

@ -1,31 +1,29 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppContainer
import com.example.myapplication.composeui.MyViewModel
import com.example.myapplication.database.entities.model.SessionFromOrder
import com.example.myapplication.database.entities.repository.OrderRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class OrderViewModel(
savedStateHandle: SavedStateHandle,
private val orderRepository: OrderRepository
) : ViewModel() {
) : MyViewModel() {
private val orderUid: Int = checkNotNull(savedStateHandle["id"])
val orderUiState: StateFlow<OrderUiState> =
flow { emit(orderRepository.getOrder(orderUid)) }.map {
OrderUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppContainer.TIMEOUT),
initialValue = OrderUiState()
)
var orderUiState by mutableStateOf(OrderUiState())
private set
suspend fun refreshState() {
runInScope(actionSuccess = {
orderUiState = OrderUiState(orderRepository.getOrder(orderUid))
}, actionError = {
orderUiState = OrderUiState()
})
}
}
data class OrderUiState(val sessionList: List<SessionFromOrder> = listOf())

View File

@ -1,7 +1,7 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import com.example.myapplication.LiveStore
import com.example.myapplication.composeui.MyViewModel
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCinema
import com.example.myapplication.database.entities.model.UserSessionCrossRef
@ -11,25 +11,35 @@ import com.example.myapplication.database.entities.repository.UserSessionReposit
class SessionListViewModel(
private val sessionRepository: SessionRepository,
private val userSessionRepository: UserSessionRepository
) : ViewModel() {
) : MyViewModel() {
suspend fun deleteSession(session: SessionFromCinema) {
sessionRepository.deleteSession(
Session(
uid = session.uid,
dateTime = session.dateTime,
price = session.price,
maxCount = 0,
cinemaId = 0
runInScope(actionSuccess = {
sessionRepository.deleteSession(
Session(
uid = session.uid,
dateTime = session.dateTime,
price = session.price,
maxCount = 0,
cinemaId = 0
)
)
)
})
}
suspend fun addSessionInCart(sessionId: Int, count: Int = 1) {
try {
val userId: Int = LiveStore.user.value?.uid ?: return
userSessionRepository.insertUserSession(UserSessionCrossRef(userId, sessionId, count))
} catch (_: Exception) {
val userId: Int = LiveStore.user.value?.uid ?: return
runInScope(actionSuccess = {
try {
userSessionRepository.insertUserSession(
UserSessionCrossRef(
userId,
sessionId,
count
)
)
} catch (_: Exception) {
}
}
})
}
}

View File

@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.LiveStore
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.datastore.DataStoreManager
import kotlinx.coroutines.launch
@ -47,11 +48,15 @@ fun UserProfile(
var isRegistration by remember { mutableStateOf(false) }
val coroutine = rememberCoroutineScope()
val errorStringId: Int? = viewModel.userUiState.errorId
val errorMessage = if (errorStringId == null) "" else stringResource(errorStringId)
val errorMessage =
if (errorStringId == null || errorStringId == 0) "" else stringResource(errorStringId)
val user = LiveStore.user.observeAsState()
LazyColumn {
item {
if (errorStringId == 0) {
navController.navigate(Screen.CinemaList.route)
}
if (user.value != null) {
Column(
modifier = Modifier
@ -151,7 +156,7 @@ fun UserProfile(
if (isRegistration) {
Button(
onClick = { coroutine.launch { isRegistration = !viewModel.signUp() } },
onClick = { coroutine.launch { viewModel.signUp() } },
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
@ -169,13 +174,7 @@ fun UserProfile(
)
} else {
Button(
onClick = {
coroutine.launch {
if (viewModel.signIn(dataStoreManager)) {
navController.popBackStack()
}
}
},
onClick = { coroutine.launch { viewModel.signIn(dataStoreManager) } },
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)

View File

@ -1,17 +1,19 @@
package com.example.myapplication.database.entities.composeui
import android.widget.Toast
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.myapplication.MainComposeActivity
import com.example.myapplication.R
import com.example.myapplication.composeui.MyViewModel
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.datastore.DataStoreManager
class UserProfileViewModel(
private val userRepository: UserRepository
) : ViewModel() {
) : MyViewModel() {
var userUiState by mutableStateOf(UserUiState())
private set
@ -22,43 +24,62 @@ class UserProfileViewModel(
)
}
suspend fun signIn(dataStoreManager: DataStoreManager): Boolean {
suspend fun signIn(dataStoreManager: DataStoreManager) {
userUiState.details.passwordConfirm = userUiState.details.password
var errorId: Int? = validateInput(userUiState.details)
if (errorId == null) {
val overlap = userRepository.getUser(userUiState.details.login)
if (overlap == null || userUiState.details.password != overlap.password) {
errorId = R.string.err_04
}
}
userUiState = UserUiState(
details = userUiState.details,
errorId = errorId
)
if (errorId == null) {
dataStoreManager.setLogin(userUiState.details.login)
return true
}
return false
runInScope(
actionSuccess = {
if (errorId == null) {
val overlap: User? = userRepository.getUser(userUiState.details.login)
if (overlap == null || userUiState.details.password != overlap.password) {
errorId = R.string.err_04
} else {
dataStoreManager.setLogin(userUiState.details.login)
errorId = 0
}
}
userUiState = UserUiState(
details = userUiState.details,
errorId = errorId
)
}, actionError = {
errorId = R.string.err_06
userUiState = UserUiState(
details = userUiState.details,
errorId = errorId
)
})
}
suspend fun signUp(): Boolean {
suspend fun signUp() {
var errorId: Int? = validateInput(userUiState.details)
if (errorId == null) {
val overlap = userRepository.getUser(userUiState.details.login)
if (overlap != null) {
errorId = R.string.err_03
runInScope(actionSuccess = {
if (errorId == null) {
val overlap = userRepository.getUser(userUiState.details.login)
if (overlap != null) {
errorId = R.string.err_03
}
}
}
userUiState = UserUiState(
details = userUiState.details,
errorId = errorId
)
if (errorId == null) {
userRepository.insertUser(userUiState.details.toUser())
return true
}
return false
userUiState = UserUiState(
details = userUiState.details,
errorId = errorId
)
if (errorId == null) {
userRepository.insertUser(userUiState.details.toUser())
val toast = Toast.makeText(
MainComposeActivity.appContext,
"Вы зарегистрированы",
Toast.LENGTH_SHORT
)
toast.show()
}
}, actionError = {
errorId = R.string.err_06
userUiState = UserUiState(
details = userUiState.details,
errorId = errorId
)
})
}
private fun validateInput(details: UserDetails = userUiState.details): Int? {

View File

@ -22,6 +22,9 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.composeui.ErrorPlaceholder
import com.example.myapplication.composeui.LoadingPlaceholder
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.launch
@ -32,17 +35,25 @@ fun CinemaEdit(
viewModel: CinemaEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val coroutineScope = rememberCoroutineScope()
CinemaEdit(
cinemaUiState = viewModel.cinemaUiState,
onClick = {
coroutineScope.launch {
viewModel.saveCinema()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
)
when (viewModel.apiStatus) {
ApiStatus.DONE -> {
CinemaEdit(
cinemaUiState = viewModel.cinemaUiState,
onClick = {
coroutineScope.launch {
viewModel.saveCinema()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
)
}
ApiStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.popBackStack() }
)
}
}
@Composable

View File

@ -24,6 +24,9 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.composeui.ErrorPlaceholder
import com.example.myapplication.composeui.LoadingPlaceholder
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import kotlinx.coroutines.launch
import org.threeten.bp.Instant
@ -39,16 +42,26 @@ fun SessionEdit(
viewModel: SessionEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val coroutineScope = rememberCoroutineScope()
SessionEdit(
sessionUiState = viewModel.sessionUiState,
onClick = {
coroutineScope.launch {
viewModel.saveSession()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
when (viewModel.apiStatus) {
ApiStatus.DONE -> {
SessionEdit(
sessionUiState = viewModel.sessionUiState,
onClick = {
coroutineScope.launch {
viewModel.saveSession()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
ApiStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.popBackStack() }
)
}
}
fun Long.toLocalDate(): org.threeten.bp.LocalDate {

View File

@ -25,6 +25,7 @@
<string name="err_03">Логин занят</string>
<string name="err_04">Неверный логин или пароль</string>
<string name="err_05">Не совпадают пароли</string>
<string name="err_06">Ошибка сети</string>
<string name="back">Назад</string>
<string name="loading">Загрузка…</string>
</resources>

File diff suppressed because it is too large Load Diff