регистрация и вход

This commit is contained in:
dasha 2023-12-13 19:12:51 +04:00
parent 167d5ccc7d
commit 28fd92559c
33 changed files with 517 additions and 70393 deletions

View File

@ -50,6 +50,7 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
} }
} }
buildToolsVersion = "34.0.0"
} }
dependencies { dependencies {
@ -58,6 +59,9 @@ dependencies {
implementation("io.github.vanpra.compose-material-dialogs:datetime:0.8.1-rc") implementation("io.github.vanpra.compose-material-dialogs:datetime:0.8.1-rc")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
// LiveData
implementation("androidx.compose.runtime:runtime-livedata:1.5.4")
// Core // Core
implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")

View File

@ -0,0 +1,11 @@
package com.example.myapplication
import androidx.lifecycle.MutableLiveData
import com.example.myapplication.database.entities.model.User
class LiveStore {
companion object {
val user = MutableLiveData<User?>(null)
val searchRequest = MutableLiveData("")
}
}

View File

@ -3,12 +3,17 @@ package com.example.myapplication
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.example.myapplication.composeui.Authenticator
import com.example.myapplication.composeui.navigation.MainNavbar import com.example.myapplication.composeui.navigation.MainNavbar
import com.example.myapplication.datastore.DataStoreManager import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.ui.theme.PmudemoTheme import com.example.myapplication.ui.theme.PmudemoTheme
@ -22,14 +27,15 @@ class MainComposeActivity : ComponentActivity() {
setContent { setContent {
PmudemoTheme(darkTheme = isDarkTheme.value) { PmudemoTheme(darkTheme = isDarkTheme.value) {
LaunchedEffect(key1 = true) { LaunchedEffect(key1 = true) {
dataStoreManager.getSettings().collect { setting -> dataStoreManager.getDarkTheme().collect { setting ->
isDarkTheme.value = setting.isDarkTheme isDarkTheme.value = setting == "Dark"
} }
} }
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Authenticator(dataStoreManager)
MainNavbar( MainNavbar(
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
dataStoreManager = dataStoreManager dataStoreManager = dataStoreManager

View File

@ -87,12 +87,22 @@ interface MyServerService {
@Path("id") id: Int, @Path("id") id: Int,
): UserRemote ): UserRemote
@GET("users?_limit=1")
suspend fun getUser(
@Query("login") login: String,
): List<UserRemote>
@PUT("users/{id}") @PUT("users/{id}")
suspend fun updateUserCart( suspend fun updateUserCart(
@Path("id") id: Int, @Path("id") id: Int,
@Body userRemote: UserRemote, @Body userRemote: UserRemote,
): UserRemote ): UserRemote
@POST("users")
suspend fun createUser(
@Body user: UserRemote,
): UserRemote
@GET("orders") @GET("orders")
suspend fun getOrders( suspend fun getOrders(
@Query("_page") page: Int, @Query("_page") page: Int,
@ -116,7 +126,7 @@ interface MyServerService {
): OrderRemote ): OrderRemote
companion object { companion object {
//private const val BASE_URL = "http://192.168.154.166:8079/" //private const val BASE_URL = "http://192.168.154.166:8080/"
private const val BASE_URL = "http://192.168.0.101:8079/" private const val BASE_URL = "http://192.168.0.101:8079/"
@Volatile @Volatile

View File

@ -2,17 +2,23 @@ package com.example.myapplication.api.order
import com.example.myapplication.api.session.SessionFromOrderRemote import com.example.myapplication.api.session.SessionFromOrderRemote
import com.example.myapplication.database.entities.model.Order import com.example.myapplication.database.entities.model.Order
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable @Serializable
data class OrderRemote( data class OrderRemote(
val id: Int = 0, val userId: Int = 0, var sessions: List<SessionFromOrderRemote> = emptyList() val id: Int = 0,
val userId: Int = 0,
@Contextual
val dateTime: LocalDateTime = LocalDateTime.now(),
var sessions: List<SessionFromOrderRemote> = emptyList()
) )
fun OrderRemote.toOrder(): Order = Order( fun OrderRemote.toOrder(): Order = Order(
id, userId id, userId, dateTime
) )
fun Order.toOrderRemote(): OrderRemote = OrderRemote( fun Order.toOrderRemote(): OrderRemote = OrderRemote(
uid, userId!!, sessions = emptyList() uid, userId!!, dateTime, sessions = emptyList()
) )

View File

@ -2,6 +2,8 @@ package com.example.myapplication.api.user
import android.util.Log import android.util.Log
import com.example.myapplication.api.MyServerService import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.cinema.toCinema
import com.example.myapplication.api.cinema.toCinemaRemote
import com.example.myapplication.api.session.toSessionFromCart import com.example.myapplication.api.session.toSessionFromCart
import com.example.myapplication.database.entities.model.SessionFromCart import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.User import com.example.myapplication.database.entities.model.User
@ -21,6 +23,10 @@ class RestUserRepository(
return dbUserRepository.getAllUsers() return dbUserRepository.getAllUsers()
} }
override suspend fun getUser(login: String): User? {
return service.getUser(login).firstOrNull()?.toUser()
}
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> { override suspend fun getCartByUser(userId: Int): List<SessionFromCart> {
val cart = service.getUserCart(userId) val cart = service.getUserCart(userId)
dbUserSessionRepository.deleteUserSessions(userId) dbUserSessionRepository.deleteUserSessions(userId)
@ -47,6 +53,7 @@ class RestUserRepository(
} }
override suspend fun insertUser(user: User) { override suspend fun insertUser(user: User) {
service.createUser(user.toUserRemote()).toUser()
} }
override suspend fun updateUser(user: User) { override suspend fun updateUser(user: User) {

View File

@ -1,6 +1,10 @@
package com.example.myapplication.api.user package com.example.myapplication.api.user
import com.example.myapplication.api.session.SessionFromCartRemote import com.example.myapplication.api.session.SessionFromCartRemote
import com.example.myapplication.api.session.SessionRemote
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.model.UserRole
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
@ -8,5 +12,20 @@ data class UserRemote(
val id: Int = 0, val id: Int = 0,
val login: String = "", val login: String = "",
val password: String = "", val password: String = "",
val role: Int = -1,
var sessions: List<SessionFromCartRemote> = emptyList() var sessions: List<SessionFromCartRemote> = emptyList()
)
fun User.toUserRemote(): UserRemote = UserRemote(
uid,
login,
password,
role = role.ordinal,
)
fun UserRemote.toUser(): User = User(
id,
login,
password,
role = enumValues<UserRole>()[role],
) )

View File

@ -0,0 +1,39 @@
package com.example.myapplication.composeui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.LiveStore
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.datastore.DataStoreManager
import kotlinx.coroutines.launch
@Composable
fun Authenticator(
dataStoreManager: DataStoreManager,
viewModel: AuthenticatorViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val login = dataStoreManager.getLogin().collectAsState(initial = "").value
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
}
}
synchronize()
}

View File

@ -0,0 +1,13 @@
package com.example.myapplication.composeui
import androidx.lifecycle.ViewModel
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.repository.UserRepository
class AuthenticatorViewModel(
private val userRepository: UserRepository
) : ViewModel() {
suspend fun findUserByLogin(login: String): User? {
return userRepository.getUser(login)
}
}

View File

@ -36,6 +36,8 @@ import androidx.compose.material3.rememberDismissState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf 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
@ -51,14 +53,13 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.LiveStore
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.database.entities.composeui.AppViewModelProvider import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.database.entities.composeui.CartUiState
import com.example.myapplication.database.entities.composeui.CartViewModel
import com.example.myapplication.database.entities.model.Session import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCart import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.UserRole
import com.example.myapplication.ui.theme.PmudemoTheme import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
@ -77,7 +78,7 @@ fun Cart(
cartUiState = cartUiState, cartUiState = cartUiState,
modifier = Modifier modifier = Modifier
.padding(all = 10.dp), .padding(all = 10.dp),
onSwipe = { session: SessionFromCart, user: Int -> onSwipe = { session: SessionFromCart ->
coroutineScope.launch { coroutineScope.launch {
viewModel.removeFromCart( viewModel.removeFromCart(
session = Session( session = Session(
@ -86,11 +87,11 @@ fun Cart(
price = session.price, price = session.price,
maxCount = 0, maxCount = 0,
cinemaId = session.cinemaId cinemaId = session.cinemaId
), user = user )
) )
} }
}, },
onChangeCount = { session: SessionFromCart, user: Int, count: Int -> onChangeCount = { session: SessionFromCart, count: Int ->
coroutineScope.launch { coroutineScope.launch {
viewModel.updateFromCart( viewModel.updateFromCart(
session = Session( session = Session(
@ -99,13 +100,13 @@ fun Cart(
price = session.price, price = session.price,
maxCount = 0, maxCount = 0,
cinemaId = session.cinemaId cinemaId = session.cinemaId
), userId = user, count = count, availableCount = session.availableCount ), count = count, availableCount = session.availableCount
) )
} }
}, },
onAddToOrder = { sessions: List<SessionFromCart>, user: Int -> onAddToOrder = { sessions: List<SessionFromCart> ->
coroutineScope.launch { coroutineScope.launch {
viewModel.addToOrder(sessions = sessions, userId = user) viewModel.addToOrder(sessions = sessions)
} }
} }
) )
@ -116,9 +117,9 @@ fun Cart(
private fun Cart( private fun Cart(
cartUiState: CartUiState, cartUiState: CartUiState,
modifier: Modifier, modifier: Modifier,
onSwipe: (SessionFromCart, Int) -> Unit, onSwipe: (SessionFromCart) -> Unit,
onChangeCount: (SessionFromCart, Int, Int) -> Unit, onChangeCount: (SessionFromCart, Int) -> Unit,
onAddToOrder: (List<SessionFromCart>, Int) -> Unit onAddToOrder: (List<SessionFromCart>) -> Unit
) { ) {
LazyColumn( LazyColumn(
modifier = modifier modifier = modifier
@ -129,68 +130,8 @@ private fun Cart(
) )
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) { if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
onSwipe(session, 1) onSwipe(session)
} }
SwipeToDelete(
dismissState = dismissState,
session = session,
onChangeCount = onChangeCount
)
}
}
Column {
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { onAddToOrder(cartUiState.sessionList, 1) },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) { Text("Купить") }
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
session: SessionFromCart,
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
) {
SwipeToDismiss(
state = dismissState,
directions = setOf(
DismissDirection.EndToStart
),
background = {
val backgroundColor by animateColorAsState(
when (dismissState.targetValue) {
DismissValue.DismissedToStart -> Color.Red.copy(alpha = 0.8f)
else -> MaterialTheme.colorScheme.background
},
label = ""
)
val iconScale by animateFloatAsState(
targetValue = if (dismissState.targetValue == DismissValue.DismissedToStart) 1.3f else 0.5f,
label = ""
)
Box(
Modifier
.fillMaxSize()
.background(color = backgroundColor)
.padding(end = 16.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
modifier = Modifier.scale(iconScale),
imageVector = Icons.Outlined.Delete,
contentDescription = "Delete",
tint = Color.White
)
}
},
dismissContent = {
SessionListItem( SessionListItem(
session = session, session = session,
modifier = Modifier modifier = Modifier
@ -201,16 +142,29 @@ private fun SwipeToDelete(
onChangeCount = onChangeCount onChangeCount = onChangeCount
) )
} }
) }
val user = LiveStore.user.observeAsState()
if (user.value?.role == UserRole.USER) {
Column {
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { onAddToOrder(cartUiState.sessionList) },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) { Text("Купить") }
}
}
} }
@Composable @Composable
private fun SessionListItem( private fun SessionListItem(
session: SessionFromCart, session: SessionFromCart,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onChangeCount: (SessionFromCart, Int, Int) -> Unit, onChangeCount: (SessionFromCart, Int) -> Unit,
) { ) {
var currentCount by remember { mutableStateOf(session.count) } var currentCount by remember { mutableIntStateOf(session.count) }
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm") val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formattedDate = dateFormatter.format(session.dateTime) val formattedDate = dateFormatter.format(session.dateTime)
@ -265,7 +219,7 @@ private fun SessionListItem(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
IconButton( IconButton(
onClick = { onChangeCount(session, 1, --currentCount) } onClick = { onChangeCount(session, --currentCount) }
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.minus), imageVector = ImageVector.vectorResource(id = R.drawable.minus),
@ -284,7 +238,6 @@ private fun SessionListItem(
onClick = { onClick = {
onChangeCount( onChangeCount(
session, session,
1,
if (currentCount != session.availableCount) ++currentCount else currentCount if (currentCount != session.availableCount) ++currentCount else currentCount
) )
} }

View File

@ -1,9 +1,10 @@
package com.example.myapplication.database.entities.composeui package com.example.myapplication.composeui
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
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.example.myapplication.LiveStore
import com.example.myapplication.database.entities.model.Order import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.OrderSessionCrossRef import com.example.myapplication.database.entities.model.OrderSessionCrossRef
import com.example.myapplication.database.entities.model.Session import com.example.myapplication.database.entities.model.Session
@ -13,7 +14,7 @@ import com.example.myapplication.database.entities.repository.OrderRepository
import com.example.myapplication.database.entities.repository.OrderSessionRepository import com.example.myapplication.database.entities.repository.OrderSessionRepository
import com.example.myapplication.database.entities.repository.UserRepository import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository import com.example.myapplication.database.entities.repository.UserSessionRepository
import kotlinx.coroutines.delay import org.threeten.bp.LocalDateTime
class CartViewModel( class CartViewModel(
private val userSessionRepository: UserSessionRepository, private val userSessionRepository: UserSessionRepository,
@ -21,19 +22,20 @@ class CartViewModel(
private val orderSessionRepository: OrderSessionRepository, private val orderSessionRepository: OrderSessionRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
) : ViewModel() { ) : ViewModel() {
private val userUid: Int = 1
var cartUiState by mutableStateOf(CartUiState()) var cartUiState by mutableStateOf(CartUiState())
private set private set
suspend fun refreshState() { suspend fun refreshState() {
val cart = userRepository.getCartByUser(userUid) val userId: Int = LiveStore.user.value?.uid ?: return
val cart = userRepository.getCartByUser(userId)
cartUiState = CartUiState(cart) cartUiState = CartUiState(cart)
} }
suspend fun addToOrder(userId: Int, sessions: List<SessionFromCart>) { suspend fun addToOrder(sessions: List<SessionFromCart>) {
val userId: Int = LiveStore.user.value?.uid ?: return
if (sessions.isEmpty()) if (sessions.isEmpty())
return return
val orderId = orderRepository.insertOrder(Order(0, userId)) val orderId = orderRepository.insertOrder(Order(0, userId, LocalDateTime.now()))
sessions.forEach { session -> sessions.forEach { session ->
orderSessionRepository.insertOrderSession( orderSessionRepository.insertOrderSession(
OrderSessionCrossRef( OrderSessionCrossRef(
@ -48,15 +50,17 @@ class CartViewModel(
refreshState() refreshState()
} }
suspend fun removeFromCart(user: Int, session: Session, count: Int = 1) { suspend fun removeFromCart(session: Session, count: Int = 0) {
userSessionRepository.deleteUserSession(UserSessionCrossRef(user, session.uid, count)) val userId: Int = LiveStore.user.value?.uid ?: return
userSessionRepository.deleteUserSession(UserSessionCrossRef(userId, session.uid, count))
refreshState() refreshState()
} }
suspend fun updateFromCart(userId: Int, session: Session, count: Int, availableCount: Int) suspend fun updateFromCart(session: Session, count: Int, availableCount: Int)
: Boolean { : Boolean {
val userId: Int = LiveStore.user.value?.uid ?: return false
if (count == 0) { if (count == 0) {
removeFromCart(userId, session, count) removeFromCart(session, count)
return false return false
} }
if (count > availableCount) if (count > availableCount)

View File

@ -176,9 +176,9 @@ fun Navhost(
modifier.padding(innerPadding) modifier.padding(innerPadding)
) { ) {
composable(Screen.CinemaList.route) { CinemaList(navController) } composable(Screen.CinemaList.route) { CinemaList(navController) }
composable(Screen.OrderList.route) { OrderList(navController, 1) } composable(Screen.OrderList.route) { OrderList(navController) }
composable(Screen.Cart.route) { Cart() } composable(Screen.Cart.route) { Cart() }
composable(Screen.UserProfile.route) { UserProfile(isDarkTheme, dataStore) } composable(Screen.UserProfile.route) { UserProfile(isDarkTheme, dataStore, navController) }
composable( composable(
Screen.CinemaEdit.route, Screen.CinemaEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType }) arguments = listOf(navArgument("id") { type = NavType.IntType })

View File

@ -6,6 +6,9 @@ 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 com.example.myapplication.CinemaApplication import com.example.myapplication.CinemaApplication
import com.example.myapplication.composeui.Authenticator
import com.example.myapplication.composeui.AuthenticatorViewModel
import com.example.myapplication.composeui.CartViewModel
import com.example.myapplication.database.entities.composeui.edit.CinemaEditViewModel import com.example.myapplication.database.entities.composeui.edit.CinemaEditViewModel
import com.example.myapplication.database.entities.composeui.edit.SessionEditViewModel import com.example.myapplication.database.entities.composeui.edit.SessionEditViewModel
@ -57,6 +60,14 @@ object AppViewModelProvider {
cinemaApplication().container.orderRestRepository, cinemaApplication().container.orderRestRepository,
) )
} }
initializer {
UserProfileViewModel(
cinemaApplication().container.userRestRepository,
)
}
initializer {
AuthenticatorViewModel(cinemaApplication().container.userRestRepository)
}
} }
} }

View File

@ -1,6 +1,7 @@
package com.example.myapplication.database.entities.composeui package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.util.Log
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -27,6 +28,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope 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
@ -36,8 +38,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.example.myapplication.LiveStore
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Cinema import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.UserRole
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@ -47,22 +51,25 @@ fun CinemaList(
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val cinemaPagingItems = viewModel.cinemaListUiState.collectAsLazyPagingItems() val cinemaPagingItems = viewModel.cinemaListUiState.collectAsLazyPagingItems()
val user = LiveStore.user.observeAsState()
Scaffold( Scaffold(
topBar = {}, topBar = {},
floatingActionButton = { floatingActionButton = {
FloatingActionButton( if (user.value?.role == UserRole.ADMIN) {
onClick = { FloatingActionButton(
val route = Screen.CinemaEdit.route.replace("{id}", 0.toString()) onClick = {
navController.navigate(route) val route = Screen.CinemaEdit.route.replace("{id}", 0.toString())
}, navController.navigate(route)
containerColor = MaterialTheme.colorScheme.primary, },
) { containerColor = MaterialTheme.colorScheme.primary,
Icon( ) {
Icons.Filled.Add, Icon(
"Добавить", Icons.Filled.Add,
tint = MaterialTheme.colorScheme.onPrimary "Добавить",
) tint = MaterialTheme.colorScheme.onPrimary
)
}
} }
} }
) { innerPadding -> ) { innerPadding ->
@ -132,6 +139,7 @@ private fun CinemaListItem(
onDeleteClick: (cinema: Cinema) -> Unit, onDeleteClick: (cinema: Cinema) -> Unit,
onEditClick: (cinema: Int) -> Unit onEditClick: (cinema: Int) -> Unit
) { ) {
val user = LiveStore.user.observeAsState()
Box( Box(
modifier = modifier modifier = modifier
) { ) {
@ -160,27 +168,29 @@ private fun CinemaListItem(
color = MaterialTheme.colorScheme.onSecondary color = MaterialTheme.colorScheme.onSecondary
) )
// Добавляем пустое пространство для разделения текста и кнопки if (user.value?.role == UserRole.ADMIN) {
Spacer(modifier = Modifier.weight(1f)) // Добавляем пустое пространство для разделения текста и кнопки
IconButton( Spacer(modifier = Modifier.weight(1f))
onClick = { onEditClick(cinema.uid) }, IconButton(
modifier = Modifier.size(24.dp) onClick = { onEditClick(cinema.uid) },
) { modifier = Modifier.size(24.dp)
Icon( ) {
imageVector = Icons.Default.Edit, Icon(
contentDescription = "Редактировать", imageVector = Icons.Default.Edit,
tint = MaterialTheme.colorScheme.onSecondary, contentDescription = "Редактировать",
) tint = MaterialTheme.colorScheme.onSecondary,
} )
IconButton( }
onClick = { onDeleteClick(cinema) }, IconButton(
modifier = Modifier.size(24.dp) onClick = { onDeleteClick(cinema) },
) { modifier = Modifier.size(24.dp)
Icon( ) {
imageVector = Icons.Default.Delete, Icon(
contentDescription = "Удалить", imageVector = Icons.Default.Delete,
tint = MaterialTheme.colorScheme.onSecondary, contentDescription = "Удалить",
) tint = MaterialTheme.colorScheme.onSecondary,
)
}
} }
} }
} }

View File

@ -20,6 +20,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.livedata.observeAsState
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.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
@ -29,8 +30,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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 com.example.myapplication.LiveStore
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Cinema import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.UserRole
@Composable @Composable
fun CinemaView( fun CinemaView(
@ -38,6 +41,7 @@ fun CinemaView(
viewModel: CinemaViewModel = viewModel(factory = AppViewModelProvider.Factory), viewModel: CinemaViewModel = viewModel(factory = AppViewModelProvider.Factory),
) { ) {
val cinemaUiState = viewModel.cinemaUiState val cinemaUiState = viewModel.cinemaUiState
val user = LiveStore.user.observeAsState()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.refreshState() viewModel.refreshState()
@ -116,21 +120,22 @@ fun CinemaView(
.weight(1f) // Занимает доступное пространство .weight(1f) // Занимает доступное пространство
.padding(top = 8.dp, bottom = 8.dp) .padding(top = 8.dp, bottom = 8.dp)
) )
if (user.value?.role == UserRole.ADMIN) {
IconButton( IconButton(
onClick = { onClick = {
val route = Screen.SessionEdit.route.replace("{id}", 0.toString()) val route = Screen.SessionEdit.route.replace("{id}", 0.toString())
.replace( .replace(
"{cinemaId}", "{cinemaId}",
cinemaUiState.cinemaWithSessions?.cinema?.uid.toString() cinemaUiState.cinemaWithSessions?.cinema?.uid.toString()
) )
navController.navigate(route) navController.navigate(route)
}
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Добавить сеанс",
)
} }
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Добавить сеанс",
)
} }
} }
if (cinemaUiState.cinemaWithSessions != null) { if (cinemaUiState.cinemaWithSessions != null) {

View File

@ -21,25 +21,6 @@ class CinemaViewModel(
cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid)) cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
} }
} }
// init {
// viewModelScope.launch {
// if (cinemaUid > 0) {
// cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
// }
// }
// }
// val cinemaUiState: mutableStateOf(CinemaUiState()) = cinemaRepository.getCinema(
// cinemaUid
// ).map
// {
// CinemaUiState(it)
// }.stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
// initialValue = CinemaUiState()
// )
} }
data class CinemaUiState(val cinemaWithSessions: CinemaWithSessions? = null) data class CinemaUiState(val cinemaWithSessions: CinemaWithSessions? = null)

View File

@ -30,14 +30,13 @@ import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey import androidx.paging.compose.itemKey
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.ui.theme.PmudemoTheme import com.example.myapplication.ui.theme.PmudemoTheme
import org.threeten.bp.format.DateTimeFormatter
@Composable @Composable
fun OrderList( fun OrderList(
navController: NavController?, navController: NavController?,
userId: Int?,
viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory) viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) { ) {
val coroutineScope = rememberCoroutineScope()
val ordersUiState = viewModel.orderListUiState.collectAsLazyPagingItems() val ordersUiState = viewModel.orderListUiState.collectAsLazyPagingItems()
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
@ -48,7 +47,9 @@ fun OrderList(
key = ordersUiState.itemKey(), key = ordersUiState.itemKey(),
contentType = ordersUiState.itemContentType()) { index -> contentType = ordersUiState.itemContentType()) { index ->
val order = ordersUiState[index] val order = ordersUiState[index]
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val orderId = Screen.OrderView.route.replace("{id}", order!!.uid.toString()) val orderId = Screen.OrderView.route.replace("{id}", order!!.uid.toString())
val formattedDate = dateFormatter.format(order.dateTime)
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -66,7 +67,7 @@ fun OrderList(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text("Заказ №${order.uid}", color = MaterialTheme.colorScheme.onSecondary) Text("Заказ №${order.uid}, ${formattedDate}", color = MaterialTheme.colorScheme.onSecondary)
} }
} }
} }
@ -81,7 +82,7 @@ fun OrderListPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
OrderList(navController = null, 1) OrderList(navController = null)
} }
} }
} }

View File

@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
import com.example.myapplication.LiveStore
import com.example.myapplication.database.AppDataContainer import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.Order import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.repository.OrderRepository import com.example.myapplication.database.entities.repository.OrderRepository
@ -19,7 +20,7 @@ import kotlinx.coroutines.launch
class OrderListViewModel( class OrderListViewModel(
private val orderRepository: OrderRepository private val orderRepository: OrderRepository
) : ViewModel() { ) : ViewModel() {
val orderListUiState: Flow<PagingData<Order>> = orderRepository.getAllOrders(1) val orderListUiState: Flow<PagingData<Order>> = orderRepository.getAllOrders(LiveStore.user.value?.uid ?: 0)
} }
data class OrderListUiState(val orderList: List<Order> = listOf()) data class OrderListUiState(val orderList: List<Order> = listOf())

View File

@ -22,6 +22,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope 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
@ -31,8 +32,10 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.myapplication.LiveStore
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.UserRole
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
@ -44,6 +47,7 @@ fun SessionList(
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val cinemaWithSessions = cinemaWithSessionsViewModel.cinemaUiState.cinemaWithSessions!! val cinemaWithSessions = cinemaWithSessionsViewModel.cinemaUiState.cinemaWithSessions!!
val user = LiveStore.user.observeAsState()
LazyColumn { LazyColumn {
if (cinemaWithSessions.sessions.isEmpty()) { if (cinemaWithSessions.sessions.isEmpty()) {
@ -104,36 +108,39 @@ fun SessionList(
color = MaterialTheme.colorScheme.onSecondary color = MaterialTheme.colorScheme.onSecondary
) )
} }
if (user.value?.role == UserRole.USER) {
IconButton( IconButton(
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
if (session.availableCount != 0) if (session.availableCount != 0)
viewModel.addSessionInCart(sessionId = session.uid) viewModel.addSessionInCart(sessionId = session.uid)
} }
}, },
) { ) {
Icon( Icon(
imageVector = Icons.Filled.ShoppingCart, imageVector = Icons.Filled.ShoppingCart,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onSecondary tint = MaterialTheme.colorScheme.onSecondary
) )
}
} }
IconButton( if (user.value?.role == UserRole.ADMIN) {
onClick = { IconButton(
coroutineScope.launch { onClick = {
viewModel.deleteSession(session = session) coroutineScope.launch {
cinemaWithSessionsViewModel.refreshState() viewModel.deleteSession(session = session)
} cinemaWithSessionsViewModel.refreshState()
}, }
) { },
Icon( ) {
imageVector = Icons.Default.Delete, Icon(
contentDescription = null, imageVector = Icons.Default.Delete,
modifier = Modifier.size(24.dp), contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondary modifier = Modifier.size(24.dp),
) tint = MaterialTheme.colorScheme.onSecondary
)
}
} }
} }
} }

View File

@ -1,6 +1,7 @@
package com.example.myapplication.database.entities.composeui package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.example.myapplication.LiveStore
import com.example.myapplication.database.entities.model.Session import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCinema import com.example.myapplication.database.entities.model.SessionFromCinema
import com.example.myapplication.database.entities.model.UserSessionCrossRef import com.example.myapplication.database.entities.model.UserSessionCrossRef
@ -25,7 +26,8 @@ class SessionListViewModel(
suspend fun addSessionInCart(sessionId: Int, count: Int = 1) { suspend fun addSessionInCart(sessionId: Int, count: Int = 1) {
try { try {
userSessionRepository.insertUserSession(UserSessionCrossRef(1, sessionId, count)) val userId: Int = LiveStore.user.value?.uid ?: return
userSessionRepository.insertUserSession(UserSessionCrossRef(userId, sessionId, count))
} catch (_: Exception) { } catch (_: Exception) {
} }

View File

@ -20,29 +20,60 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp 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 com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.datastore.SettingData
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun UserProfile( fun UserProfile(
isDarkTheme: MutableState<Boolean>, isDarkTheme: MutableState<Boolean>,
dataStoreManager: DataStoreManager dataStoreManager: DataStoreManager,
navController: NavController,
viewModel: UserProfileViewModel = viewModel(factory = AppViewModelProvider.Factory),
) { ) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var isRegistration by remember { mutableStateOf(false) } var isRegistration by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val coroutine = rememberCoroutineScope()
val errorStringId: Int? = viewModel.userUiState.errorId
val errorMessage = if (errorStringId == null) "" else stringResource(errorStringId)
val user = LiveStore.user.observeAsState()
LazyColumn { LazyColumn {
item { item {
Text(
text = "Текущий пользователь: " + (LiveStore.user.value?.login ?: ""),
)
Button(
enabled = user.value != null,
onClick = {
coroutineScope.launch {
dataStoreManager.setLogin("")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text("Выход")
}
Text(
text = errorMessage,
color = Color.Red
)
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -54,8 +85,10 @@ fun UserProfile(
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
BasicTextField( BasicTextField(
value = username, value = viewModel.userUiState.details.login,
onValueChange = { newValue -> username = newValue }, onValueChange = {
viewModel.updateUiState(viewModel.userUiState.details.copy(login = it))
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.size(36.dp) .size(36.dp)
@ -68,8 +101,10 @@ fun UserProfile(
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
BasicTextField( BasicTextField(
value = password, value = viewModel.userUiState.details.password,
onValueChange = { newValue -> password = newValue }, onValueChange = {
viewModel.updateUiState(viewModel.userUiState.details.copy(password = it))
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.size(36.dp) .size(36.dp)
@ -80,7 +115,12 @@ fun UserProfile(
if (isRegistration) { if (isRegistration) {
Button( Button(
onClick = { }, onClick = {
coroutineScope.launch {
val flag = viewModel.signUp()
isRegistration = !flag
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp) .padding(8.dp)
@ -98,7 +138,13 @@ fun UserProfile(
) )
} else { } else {
Button( Button(
onClick = { }, onClick = {
coroutineScope.launch {
if (viewModel.signIn(dataStoreManager)) {
navController.navigate(Screen.CinemaList.route)
}
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp) .padding(8.dp)
@ -133,14 +179,16 @@ fun UserProfile(
.padding(5.dp) .padding(5.dp)
) )
val coroutine = rememberCoroutineScope()
Switch( Switch(
checked = isDarkTheme.value, checked = isDarkTheme.value,
onCheckedChange = { onCheckedChange = {
isDarkTheme.value = !isDarkTheme.value isDarkTheme.value = !isDarkTheme.value
coroutine.launch { coroutine.launch {
dataStoreManager.saveSettings(SettingData(isDarkTheme = isDarkTheme.value)) if (isDarkTheme.value) {
dataStoreManager.setDarkTheme("Dark")
} else {
dataStoreManager.setDarkTheme("Light")
}
} }
}, },
colors = switchColors colors = switchColors
@ -150,16 +198,3 @@ fun UserProfile(
} }
} }
} }
/*@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 UserProfilePreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
UserProfile(navController = null, isDarkTheme = remember { mutableStateOf(true) })
}
}
}*/

View File

@ -0,0 +1,90 @@
package com.example.myapplication.database.entities.composeui
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.myapplication.R
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() {
var userUiState by mutableStateOf(UserUiState())
private set
fun updateUiState(userDetails: UserDetails) {
userUiState = UserUiState(
details = userDetails,
errorId = null
)
}
suspend fun signIn(dataStoreManager: DataStoreManager): Boolean {
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
}
suspend fun signUp(): Boolean {
var errorId: Int? = validateInput(userUiState.details)
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
}
private fun validateInput(details: UserDetails = userUiState.details): Int? {
return if (details.login.isBlank()) {
R.string.err_01
} else if (details.password.isBlank()) {
R.string.err_02
} else {
null
}
}
}
data class UserDetails(
val login: String = "",
val password: String = ""
)
data class UserUiState(
val details: UserDetails = UserDetails(),
val errorId: Int? = null
)
fun UserDetails.toUser(uid: Int = 0): User = User(
uid = uid,
login = login,
password = password,
)

View File

@ -14,6 +14,9 @@ interface UserDao {
@Query("select * from users order by login collate nocase asc") @Query("select * from users order by login collate nocase asc")
fun getAll(): Flow<List<User>> fun getAll(): Flow<List<User>>
@Query("select * from users where users.login=:login LIMIT 1")
suspend fun getByLogin(login: String): User?
@Query( @Query(
"SELECT sessions.*, sessions.max_count-IFNULL(SUM(orders_sessions.count), 0) as available_count, " + "SELECT sessions.*, sessions.max_count-IFNULL(SUM(orders_sessions.count), 0) as available_count, " +
"users_sessions.count FROM sessions " + "users_sessions.count FROM sessions " +

View File

@ -4,23 +4,18 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import org.threeten.bp.LocalDateTime
@Entity( @Entity(
tableName = "orders", foreignKeys = [ tableName = "orders"
ForeignKey(
entity = User::class,
parentColumns = ["uid"],
childColumns = ["user_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
]
) )
data class Order( data class Order(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int, val uid: Int,
@ColumnInfo(name = "user_id", index = true) @ColumnInfo(name = "user_id", index = true)
val userId: Int?, val userId: Int?,
@ColumnInfo(name = "date_time")
val dateTime: LocalDateTime,
) { ) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -8,7 +8,8 @@ data class User(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int = 0, val uid: Int = 0,
val login: String, val login: String,
val password: String val password: String,
val role: UserRole = UserRole.USER
) { ) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -0,0 +1,6 @@
package com.example.myapplication.database.entities.model
enum class UserRole(val value: Int) {
USER(0),
ADMIN(1)
}

View File

@ -8,6 +8,8 @@ import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository { class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAllUsers(): Flow<List<User>> = userDao.getAll() override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override suspend fun getUser(login: String): User? = userDao.getByLogin(login)
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> = override suspend fun getCartByUser(userId: Int): List<SessionFromCart> =
userDao.getCartByUid(userId) userDao.getCartByUid(userId)

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface UserRepository { interface UserRepository {
fun getAllUsers(): Flow<List<User>> fun getAllUsers(): Flow<List<User>>
suspend fun getUser(login: String): User?
suspend fun getCartByUser(userId: Int): List<SessionFromCart> suspend fun getCartByUser(userId: Int): List<SessionFromCart>
suspend fun insertUser(user: User) suspend fun insertUser(user: User)
suspend fun updateUser(user: User) suspend fun updateUser(user: User)

View File

@ -5,21 +5,46 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("data_store") private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("data_store")
class DataStoreManager(private val context: Context) { class DataStoreManager(private val context: Context) {
suspend fun saveSettings(settingData: SettingData) {
context.dataStore.edit { pref -> companion object {
pref[booleanPreferencesKey("isDarkTheme")] = settingData.isDarkTheme private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("Store")
val DARK_THEME = stringPreferencesKey("dark_theme")
val LOGIN = stringPreferencesKey("login")
}
fun getDarkTheme(): Flow<String> {
return context.dataStore.data
.map { preferences ->
preferences[DARK_THEME] ?: "Dark"
}
}
fun getLogin(): Flow<String> {
return context.dataStore.data
.map { preferences ->
preferences[LOGIN] ?: ""
}
}
private suspend fun saveStringValue(key: Preferences.Key<String>, value: String) {
context.dataStore.edit { preferences ->
preferences[key] = value
} }
} }
fun getSettings() = context.dataStore.data.map { pref -> suspend fun setDarkTheme(darkTheme: String) {
return@map SettingData( saveStringValue(DARK_THEME, darkTheme)
pref[booleanPreferencesKey("isDarkTheme")] ?: true }
)
suspend fun setLogin(login: String) {
saveStringValue(LOGIN, login)
} }
} }

View File

@ -1,5 +0,0 @@
package com.example.myapplication.datastore
data class SettingData(
val isDarkTheme: Boolean
)

View File

@ -19,4 +19,8 @@
<string name="session_cinema_not_select">Фильм не указан</string> <string name="session_cinema_not_select">Фильм не указан</string>
<string name="size">Размер загруженного изображения: %1$dx%2$d</string> <string name="size">Размер загруженного изображения: %1$dx%2$d</string>
<string name="not_uploaded">Загрузите изображение</string> <string name="not_uploaded">Загрузите изображение</string>
<string name="err_01">Введите логин</string>
<string name="err_02">Введите пароль</string>
<string name="err_03">Логин занят</string>
<string name="err_04">Неверный логин или пароль</string>
</resources> </resources>

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,19 @@
"users": [ "users": [
{ {
"id": 1, "id": 1,
"login": "login", "login": "admin",
"password": "password", "password": "admin",
"role": 1
},
{
"id": 2,
"login": "qwe",
"password": "qwe",
"role": 0,
"sessions": [ "sessions": [
{ {
"id": 5, "id": 6,
"count": 4, "count": 7,
"cinemaId": 2 "cinemaId": 2
} }
] ]
@ -16,7 +23,8 @@
"orders": [ "orders": [
{ {
"id": 2, "id": 2,
"userId": 1, "userId": 2,
"dateTime": "19.12.2022 02:15",
"sessions": [ "sessions": [
{ {
"id": 1, "id": 1,
@ -29,15 +37,9 @@
}, },
{ {
"id": 3, "id": 3,
"userId": 1, "userId": 2,
"dateTime": "13.12.2023 19:03",
"sessions": [ "sessions": [
{
"id": 1,
"dateTime": "20.12.2022 02:15",
"frozenPrice": 150,
"count": 3,
"cinemaId": 3
},
{ {
"id": 5, "id": 5,
"dateTime": "04.04.2004 00:00", "dateTime": "04.04.2004 00:00",
@ -46,50 +48,6 @@
"cinemaId": 2 "cinemaId": 2
} }
] ]
},
{
"id": 4,
"userId": 1,
"sessions": [
{
"id": 1,
"dateTime": "20.12.2022 02:15",
"frozenPrice": 150,
"count": 1,
"cinemaId": 3
},
{
"id": 5,
"dateTime": "04.04.2004 00:00",
"frozenPrice": 1234,
"count": 1,
"cinemaId": 2
}
]
},
{
"id": 5,
"userId": 1
},
{
"id": 6,
"userId": 1,
"sessions": [
{
"id": 5,
"dateTime": "04.04.2004 00:00",
"frozenPrice": 1234,
"count": 5,
"cinemaId": 2
},
{
"id": 2,
"dateTime": "22.03.2021 03:15",
"frozenPrice": 100,
"count": 5,
"cinemaId": 3
}
]
} }
], ],
"sessions": [ "sessions": [
@ -113,11 +71,19 @@
"maxCount": 15, "maxCount": 15,
"cinemaId": 2, "cinemaId": 2,
"id": 5 "id": 5
},
{
"id": 6,
"dateTime": "22.02.2022 03:15",
"price": 100,
"maxCount": 500,
"cinemaId": 2
} }
], ],
"cinemas": [ "cinemas": [
{ {
"name": "кино", "id": 2,
"name": "кино123",
"description": "кино", "description": "кино",
"image": [ "image": [
-1, -1,
@ -39085,8 +39051,7 @@
-1, -1,
-39 -39
], ],
"year": 2010, "year": 2010
"id": 2
}, },
{ {
"id": 3, "id": 3,