This commit is contained in:
dasha 2023-12-05 15:29:15 +04:00
parent 5a18ee67fc
commit e4bc55da4e
69 changed files with 2265 additions and 682 deletions

View File

@ -10,7 +10,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.example.myapplication" applicationId = "com.example.myapplication"
minSdk = 24 minSdk = 26
targetSdk = 33 targetSdk = 33
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@ -31,6 +31,7 @@ android {
} }
} }
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
@ -51,8 +52,16 @@ android {
} }
dependencies { dependencies {
implementation("com.jakewharton.threetenabp:threetenabp:1.2.1") implementation("org.threeten:threetenbp:1.5.0")
implementation("androidx.datastore:datastore-preferences:1.0.0") implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("io.github.vanpra.compose-material-dialogs:datetime:0.8.1-rc")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
// Pagination
val paging_version = "3.2.0-rc01"
implementation("androidx.paging:paging-runtime:$paging_version")
implementation("androidx.paging:paging-compose:$paging_version")
// 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")
@ -64,7 +73,7 @@ dependencies {
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3:1.1.2")
// Room // Room
val room_version = "2.5.2" val room_version = "2.5.2"

View File

@ -1,13 +1,11 @@
package com.example.myapplication package com.example.myapplication
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.Assert.*
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.
* *

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<application <application
android:name=".CinemaApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@ -0,0 +1,14 @@
package com.example.myapplication
import android.app.Application
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDataContainer
class CinemaApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
}

View File

@ -12,7 +12,8 @@ import androidx.compose.ui.Modifier
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
import com.jakewharton.threetenabp.AndroidThreeTen
//import com.jakewharton.threetenabp.AndroidThreeTen
class MainComposeActivity : ComponentActivity() { class MainComposeActivity : ComponentActivity() {
private val dataStoreManager = DataStoreManager(this) private val dataStoreManager = DataStoreManager(this)
@ -20,7 +21,7 @@ class MainComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
application.deleteDatabase("pmy-db") application.deleteDatabase("pmy-db")
AndroidThreeTen.init(this) //AndroidThreeTen.init(this)
setContent { setContent {
PmudemoTheme(darkTheme = isDarkTheme.value) { PmudemoTheme(darkTheme = isDarkTheme.value) {
LaunchedEffect(key1 = true) { LaunchedEffect(key1 = true) {

View File

@ -2,6 +2,8 @@ package com.example.myapplication.composeui
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -9,6 +11,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.layout.size import androidx.compose.foundation.layout.size
@ -17,69 +20,202 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.DismissValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
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.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
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.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.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource 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 com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.database.AppDatabase import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.entities.model.SessionFromCart 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.SessionFromCart
import com.example.myapplication.ui.theme.PmudemoTheme import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
@Composable @Composable
fun Cart(id: Int) { fun Cart(
val context = LocalContext.current viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory)
val sessions = remember { mutableStateListOf<SessionFromCart>() } ) {
LaunchedEffect(Unit) { val coroutineScope = rememberCoroutineScope()
withContext(Dispatchers.IO) { val cartUiState by viewModel.cartUiState.collectAsState()
AppDatabase.getInstance(context).userDao().getCartByUid(id).collect { data ->
sessions.clear() Cart(
sessions.addAll(data) cartUiState = cartUiState,
modifier = Modifier
.padding(all = 10.dp),
onSwipe = { session: SessionFromCart, user: Int ->
coroutineScope.launch {
viewModel.removeFromCart(
session = Session(
uid = session.uid,
dateTime = session.dateTime,
price = session.price,
maxCount = 0,
cinemaId = session.cinemaId
), user = user
)
} }
},
onChangeCount = { session: SessionFromCart, user: Int, count: Int ->
coroutineScope.launch {
viewModel.updateFromCart(
session = Session(
uid = session.uid,
dateTime = session.dateTime,
price = session.price,
maxCount = 0,
cinemaId = session.cinemaId
), userId = user, count = count, availableCount = session.availableCount
)
}
},
onAddToOrder = { sessions: List<SessionFromCart>, user: Int ->
coroutineScope.launch {
viewModel.addToOrder(sessions = sessions, userId = user)
}
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Cart(
cartUiState: CartUiState,
modifier: Modifier,
onSwipe: (SessionFromCart, Int) -> Unit,
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
onAddToOrder: (List<SessionFromCart>, Int) -> Unit
) {
LazyColumn(
modifier = modifier
) {
items(cartUiState.sessionList, key = { it.uid.toString() }) { session ->
val dismissState: DismissState = rememberDismissState(
positionalThreshold = { 200.dp.toPx() }
)
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
onSwipe(session, 1)
}
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("Купить") }
} }
} }
LazyColumn( @OptIn(ExperimentalMaterial3Api::class)
modifier = Modifier @Composable
.padding(all = 10.dp) 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(
session = session,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondary),
onChangeCount = onChangeCount
)
}
)
}
@Composable
private fun SessionListItem(
session: SessionFromCart,
modifier: Modifier = Modifier,
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
) { ) {
items(sessions) { session ->
var currentCount by remember { mutableStateOf(session.count) } var currentCount by remember { mutableStateOf(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)
Column {
Text( Text(
text = formattedDate, text = formattedDate,
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onBackground,
) )
Box( Box(
modifier = Modifier modifier = modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondary)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -124,11 +260,7 @@ fun Cart(id: Int) {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
IconButton( IconButton(
onClick = { onClick = { onChangeCount(session, 1, --currentCount) }
if (currentCount > 0) {
currentCount--
}
}
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.minus), imageVector = ImageVector.vectorResource(id = R.drawable.minus),
@ -144,11 +276,7 @@ fun Cart(id: Int) {
) )
IconButton( IconButton(
onClick = { onClick = { onChangeCount(session, 1, ++currentCount) }
if (currentCount < session.availableCount) {
currentCount++
}
}
) { ) {
Icon( Icon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
@ -163,18 +291,6 @@ fun Cart(id: Int) {
} }
} }
} }
Column {
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) { Text("Купить") }
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -184,7 +300,7 @@ fun CartPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Cart(1) Cart()
} }
} }
} }

View File

@ -47,12 +47,14 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.example.myapplication.composeui.Cart import com.example.myapplication.composeui.Cart
import com.example.myapplication.database.entities.composeui.CinemaList
import com.example.myapplication.database.entities.composeui.CinemaView
import com.example.myapplication.database.entities.composeui.OrderList
import com.example.myapplication.database.entities.composeui.OrderView
import com.example.myapplication.database.entities.composeui.UserProfile
import com.example.myapplication.database.entities.composeui.edit.CinemaEdit
import com.example.myapplication.database.entities.composeui.edit.SessionEdit
import com.example.myapplication.datastore.DataStoreManager import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.entities.composeui.CinemaList
import com.example.myapplication.entities.composeui.CinemaView
import com.example.myapplication.entities.composeui.OrderList
import com.example.myapplication.entities.composeui.OrderView
import com.example.myapplication.user.composeui.UserProfile
@Composable @Composable
fun Topbar( fun Topbar(
@ -175,13 +177,26 @@ fun Navhost(
) { ) {
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, 1) }
composable(Screen.Cart.route) { Cart(1) } composable(Screen.Cart.route) { Cart() }
composable(Screen.UserProfile.route) { UserProfile(isDarkTheme, dataStore) } composable(Screen.UserProfile.route) { UserProfile(isDarkTheme, dataStore) }
composable(
Screen.CinemaEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) {
CinemaEdit(navController)
}
composable(
Screen.SessionEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType },
navArgument("cinemaId") { type = NavType.IntType })
) {
SessionEdit(navController)
}
composable( composable(
Screen.CinemaView.route, Screen.CinemaView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType }) arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry -> ) { backStackEntry ->
backStackEntry.arguments?.let { CinemaView(it.getInt("id")) } backStackEntry.arguments?.let { CinemaView(navController) }
} }
composable( composable(
Screen.OrderView.route, Screen.OrderView.route,

View File

@ -18,6 +18,15 @@ enum class Screen(
CinemaList( CinemaList(
"Cinema-list", R.string.Cinema_main_title, Icons.Filled.Home "Cinema-list", R.string.Cinema_main_title, Icons.Filled.Home
), ),
CinemaEdit(
"Cinema-edit/{id}", R.string.Cinema_view_title, showInBottomBar = false
),
SessionEdit(
"Session-edit/{id}/{cinemaId}", R.string.Session_view_title, showInBottomBar = false
),
CinemaView(
"Cinema-view/{id}", R.string.Cinema_view_title, showInBottomBar = false
),
SessionList( SessionList(
"Session-list", R.string.Sessions_title, showInBottomBar = false "Session-list", R.string.Sessions_title, showInBottomBar = false
), ),
@ -27,9 +36,6 @@ enum class Screen(
OrderList( OrderList(
"Order-list", R.string.Order_title, Icons.Filled.List "Order-list", R.string.Order_title, Icons.Filled.List
), ),
CinemaView(
"Cinema-view/{id}", R.string.Cinema_view_title, showInBottomBar = false
),
OrderView( OrderView(
"Order-view/{id}", R.string.Order_view_title, showInBottomBar = false "Order-view/{id}", R.string.Order_view_title, showInBottomBar = false
), ),

View File

@ -0,0 +1,49 @@
package com.example.myapplication.database
import android.content.Context
import com.example.myapplication.database.entities.repository.CinemaRepository
import com.example.myapplication.database.entities.repository.OfflineCinemaRepository
import com.example.myapplication.database.entities.repository.OfflineOrderRepository
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
import com.example.myapplication.database.entities.repository.OfflineUserRepository
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
import com.example.myapplication.database.entities.repository.OrderRepository
import com.example.myapplication.database.entities.repository.OrderSessionRepository
import com.example.myapplication.database.entities.repository.SessionRepository
import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository
interface AppContainer {
val cinemaRepository: CinemaRepository
val orderRepository: OrderRepository
val orderSessionRepository: OrderSessionRepository
val sessionRepository: SessionRepository
val userRepository: UserRepository
val userSessionRepository: UserSessionRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val cinemaRepository: CinemaRepository by lazy {
OfflineCinemaRepository(AppDatabase.getInstance(context).cinemaDao())
}
override val orderRepository: OrderRepository by lazy {
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
}
override val orderSessionRepository: OrderSessionRepository by lazy {
OfflineOrderSessionRepository(AppDatabase.getInstance(context).orderSessionCrossRefDao())
}
override val sessionRepository: SessionRepository by lazy {
OfflineSessionRepository(AppDatabase.getInstance(context).sessionDao())
}
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
override val userSessionRepository: UserSessionRepository by lazy {
OfflineUserSessionRepository(AppDatabase.getInstance(context).userSessionCrossRefDao())
}
companion object {
const val TIMEOUT = 5000L
}
}

View File

@ -2,25 +2,24 @@ package com.example.myapplication.database
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.entities.dao.CinemaDao import com.example.myapplication.database.entities.dao.CinemaDao
import com.example.myapplication.entities.dao.OrderDao import com.example.myapplication.database.entities.dao.OrderDao
import com.example.myapplication.entities.dao.OrderSessionCrossRefDao import com.example.myapplication.database.entities.dao.OrderSessionCrossRefDao
import com.example.myapplication.entities.dao.SessionDao import com.example.myapplication.database.entities.dao.SessionDao
import com.example.myapplication.entities.dao.UserDao import com.example.myapplication.database.entities.dao.UserDao
import com.example.myapplication.entities.dao.UserSessionCrossRefDao import com.example.myapplication.database.entities.dao.UserSessionCrossRefDao
import com.example.myapplication.entities.model.Cinema import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.entities.model.LocalDateTimeConverter import com.example.myapplication.database.entities.model.LocalDateTimeConverter
import com.example.myapplication.entities.model.Order import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.entities.model.OrderSessionCrossRef import com.example.myapplication.database.entities.model.OrderSessionCrossRef
import com.example.myapplication.entities.model.Session import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.entities.model.User import com.example.myapplication.database.entities.model.User
import com.example.myapplication.entities.model.UserSessionCrossRef import com.example.myapplication.database.entities.model.UserSessionCrossRef
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -58,12 +57,30 @@ abstract class AppDatabase : RoomDatabase() {
userDao.insert(user2) userDao.insert(user2)
// Cinemas // Cinemas
val cinemaDao = database.cinemaDao() val cinemaDao = database.cinemaDao()
val cinema1 = Cinema(1, "BLUE 1", "Desc1", createColoredImage(Color.BLUE), 2023) val cinema1 =
val cinema2 = Cinema(2, "GREEN 2", "Desc2", createColoredImage(Color.GREEN), 2023) Cinema(1, "a", "Desc1", createColoredImage(android.graphics.Color.BLUE), 2023)
val cinema3 = Cinema(3, "RED 3", "Desc3", createColoredImage(Color.RED), 2023) val cinema2 =
Cinema(2, "b", "Desc2", createColoredImage(android.graphics.Color.GREEN), 2023)
val cinema3 =
Cinema(3, "c", "Desc3", createColoredImage(android.graphics.Color.RED), 2023)
val cinema4 =
Cinema(4, "d", "Desc4", createColoredImage(android.graphics.Color.CYAN), 2023)
cinemaDao.insert(cinema1) cinemaDao.insert(cinema1)
cinemaDao.insert(cinema2) cinemaDao.insert(cinema2)
cinemaDao.insert(cinema3) cinemaDao.insert(cinema3)
cinemaDao.insert(cinema4)
for (i in 5..20) {
val cinema = Cinema(
uid = i,
name = generateCinemaName(i),
description = "Description $i",
image = createColoredImage(getRandomColorInt()),
year = 2023
)
cinemaDao.insert(cinema)
}
// Orders // Orders
val orderDao = database.orderDao() val orderDao = database.orderDao()
val order1 = Order(1, 1) val order1 = Order(1, 1)
@ -79,28 +96,32 @@ abstract class AppDatabase : RoomDatabase() {
val session1 = Session(1, LocalDateTime.now(), 150.0, 120, cinema1.uid) val session1 = Session(1, LocalDateTime.now(), 150.0, 120, cinema1.uid)
val session2 = Session(2, LocalDateTime.now(), 200.0, 110, cinema2.uid) val session2 = Session(2, LocalDateTime.now(), 200.0, 110, cinema2.uid)
val session3 = Session(3, LocalDateTime.now(), 300.0, 100, cinema3.uid) val session3 = Session(3, LocalDateTime.now(), 300.0, 100, cinema3.uid)
val session4 = Session(4, LocalDateTime.now(), 320.0, 1150, cinema1.uid) val session4 = Session(4, LocalDateTime.now(), 450.0, 200, cinema1.uid)
sessionDao.insert(session1) sessionDao.insert(session1)
sessionDao.insert(session2) sessionDao.insert(session2)
sessionDao.insert(session3) sessionDao.insert(session3)
sessionDao.insert(session4) sessionDao.insert(session4)
// OrderSessionCrossRef для связи заказов с сеансами // OrderSessionCrossRef для связи заказов с сеансами
val orderSessionCrossRefDao = database.orderSessionCrossRefDao() val orderSessionCrossRefDao = database.orderSessionCrossRefDao()
if (session1.uid != null && session2.uid != null && session3.uid != null && session4.uid != null) { if (session1.uid != null && session2.uid != null && session3.uid != null) {
val orderSessionCrossRef1 = OrderSessionCrossRef(order1.uid, session3.uid, 150.0, 5) val orderSessionCrossRef1 =
val orderSessionCrossRef2 = OrderSessionCrossRef(order1.uid, session2.uid, 300.0, 10) OrderSessionCrossRef(order1.uid, session3.uid, 150.0, 5)
val orderSessionCrossRef3 = OrderSessionCrossRef(order2.uid, session2.uid, 350.0, 6) val orderSessionCrossRef2 =
val orderSessionCrossRef4 = OrderSessionCrossRef(order3.uid, session1.uid, 250.0, 10) OrderSessionCrossRef(order1.uid, session2.uid, 300.0, 10)
val orderSessionCrossRef5 = OrderSessionCrossRef(order3.uid, session3.uid, 150.0, 16) val orderSessionCrossRef3 =
val orderSessionCrossRef6 = OrderSessionCrossRef(order4.uid, session3.uid, 150.0, 2) OrderSessionCrossRef(order2.uid, session2.uid, 350.0, 6)
//val orderSessionCrossRef7 = OrderSessionCrossRef(order4.uid, session4.uid, 110.0, 1) val orderSessionCrossRef4 =
OrderSessionCrossRef(order3.uid, session1.uid, 250.0, 10)
val orderSessionCrossRef5 =
OrderSessionCrossRef(order3.uid, session3.uid, 150.0, 16)
val orderSessionCrossRef6 =
OrderSessionCrossRef(order4.uid, session3.uid, 150.0, 2)
orderSessionCrossRefDao.insert(orderSessionCrossRef1) orderSessionCrossRefDao.insert(orderSessionCrossRef1)
orderSessionCrossRefDao.insert(orderSessionCrossRef2) orderSessionCrossRefDao.insert(orderSessionCrossRef2)
orderSessionCrossRefDao.insert(orderSessionCrossRef3) orderSessionCrossRefDao.insert(orderSessionCrossRef3)
orderSessionCrossRefDao.insert(orderSessionCrossRef4) orderSessionCrossRefDao.insert(orderSessionCrossRef4)
orderSessionCrossRefDao.insert(orderSessionCrossRef5) orderSessionCrossRefDao.insert(orderSessionCrossRef5)
orderSessionCrossRefDao.insert(orderSessionCrossRef6) orderSessionCrossRefDao.insert(orderSessionCrossRef6)
//orderSessionCrossRefDao.insert(orderSessionCrossRef7)
} }
// UserSessions // UserSessions
val userSessionCrossRefDao = database.userSessionCrossRefDao() val userSessionCrossRefDao = database.userSessionCrossRefDao()
@ -143,5 +164,25 @@ abstract class AppDatabase : RoomDatabase() {
return stream.toByteArray() return stream.toByteArray()
} }
fun getRandomColorInt(): Int {
val red = (0..255).random()
val green = (0..255).random()
val blue = (0..255).random()
return (0xFF shl 24) or (red shl 16) or (green shl 8) or blue
}
fun generateCinemaName(index: Int): String {
val base = 'a'.toInt()
val alphabetSize = 26
val sb = StringBuilder()
var remainder = index
do {
val letter = (remainder % alphabetSize + base).toChar()
sb.insert(0, letter)
remainder /= alphabetSize
} while (remainder > 0)
return sb.toString()
}
} }
} }

View File

@ -0,0 +1,64 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.myapplication.CinemaApplication
import com.example.myapplication.database.entities.composeui.edit.CinemaEditViewModel
import com.example.myapplication.database.entities.composeui.edit.SessionEditViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
CinemaListViewModel(cinemaApplication().container.cinemaRepository)
}
initializer {
CinemaEditViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.cinemaRepository
)
}
initializer {
CinemaViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.cinemaRepository,
)
}
initializer {
SessionListViewModel(
cinemaApplication().container.sessionRepository,
cinemaApplication().container.userSessionRepository,
)
}
initializer {
SessionEditViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.sessionRepository,
)
}
initializer {
CartViewModel(
cinemaApplication().container.userSessionRepository,
cinemaApplication().container.orderRepository,
cinemaApplication().container.orderSessionRepository,
cinemaApplication().container.userRepository,
)
}
initializer {
OrderListViewModel(
cinemaApplication().container.orderRepository,
)
}
initializer {
OrderViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.orderRepository,
)
}
}
}
fun CreationExtras.cinemaApplication(): CinemaApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as CinemaApplication)

View File

@ -0,0 +1,70 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.UserSessionCrossRef
import com.example.myapplication.database.entities.repository.OrderRepository
import com.example.myapplication.database.entities.repository.OrderSessionRepository
import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class CartViewModel(
private val userSessionRepository: UserSessionRepository,
private val orderRepository: OrderRepository,
private val orderSessionRepository: OrderSessionRepository,
private val userRepository: UserRepository,
) : ViewModel() {
private val userUid: Int = 1
val cartUiState: StateFlow<CartUiState> = userRepository.getCartByUser(userUid).map {
CartUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = CartUiState()
)
suspend fun addToOrder(userId: Int, sessions: List<SessionFromCart>) {
if (sessions.isEmpty())
return
val orderId = orderRepository.insertOrder(Order(0, userId))
sessions.forEach { session ->
orderSessionRepository.insertOrderSession(
OrderSessionCrossRef(
orderId.toInt(),
session.uid,
session.price,
session.count
)
)
}
userSessionRepository.deleteUserSessions(userId)
}
suspend fun removeFromCart(user: Int, session: Session, count: Int = 1) {
userSessionRepository.deleteUserSession(UserSessionCrossRef(user, session.uid, count))
}
suspend fun updateFromCart(userId: Int, session: Session, count: Int, availableCount: Int)
: Boolean {
if (count == 0) {
removeFromCart(userId, session, count)
return false
}
if (count > availableCount)
return false
userSessionRepository.updateUserSession(UserSessionCrossRef(userId, session.uid, count))
return true
}
}
data class CartUiState(val sessionList: List<SessionFromCart> = listOf())

View File

@ -0,0 +1,198 @@
package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Cinema
import kotlinx.coroutines.launch
@Composable
fun CinemaList(
navController: NavController,
viewModel: CinemaListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val cinemaPagingItems = viewModel.cinemaPagerState.cinemaPagingData.collectAsLazyPagingItems()
fun findCinemas() {
coroutineScope.launch {
viewModel.findCinemas()
}
}
LaunchedEffect(1) {
findCinemas()
}
Scaffold(
topBar = {},
floatingActionButton = {
FloatingActionButton(
onClick = {
val route = Screen.CinemaEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
},
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
Icons.Filled.Add,
"Добавить",
tint = MaterialTheme.colorScheme.onPrimary
)
}
}
) { innerPadding ->
CinemaList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
pagingCinema = cinemaPagingItems,
onClick = { uid: Int ->
val route = Screen.CinemaView.route.replace("{id}", uid.toString())
navController.navigate(route)
},
onDeleteClick = { cinema: Cinema ->
coroutineScope.launch {
viewModel.deleteCinema(cinema)
}
},
onEditClick = { uid: Int ->
val route = Screen.CinemaEdit.route.replace("{id}", uid.toString())
navController.navigate(route)
},
)
}
}
@Composable
private fun CinemaList(
modifier: Modifier = Modifier,
pagingCinema: LazyPagingItems<Cinema>,
onClick: (uid: Int) -> Unit,
onDeleteClick: (cinema: Cinema) -> Unit,
onEditClick: (cinema: Int) -> Unit
) {
Column(
modifier = modifier
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
) {
items(pagingCinema.itemCount) { index ->
val cinema = pagingCinema[index]
if (cinema != null) {
CinemaListItem(
cinema = cinema,
modifier = Modifier
.padding(vertical = 7.dp)
.clickable { onClick(cinema.uid) }
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
),
onDeleteClick = onDeleteClick,
onEditClick = onEditClick,
)
}
}
}
}
}
@Composable
private fun CinemaListItem(
cinema: Cinema,
modifier: Modifier = Modifier,
onDeleteClick: (cinema: Cinema) -> Unit,
onEditClick: (cinema: Int) -> Unit
) {
Box(
modifier = modifier
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (cinema.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
cinema.image,
0,
cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Text(
"${cinema.name}, ${cinema.year}",
color = MaterialTheme.colorScheme.onSecondary
)
// Добавляем пустое пространство для разделения текста и кнопки
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = { onEditClick(cinema.uid) },
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "Редактировать",
tint = MaterialTheme.colorScheme.onSecondary,
)
}
IconButton(
onClick = { onDeleteClick(cinema) },
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить",
tint = MaterialTheme.colorScheme.onSecondary,
)
}
}
}
}

View File

@ -0,0 +1,43 @@
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.ViewModel
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.repository.CinemaRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
class CinemaListViewModel(
private val cinemaRepository: CinemaRepository
) : ViewModel() {
private val pagingConfig = PagingConfig(
pageSize = 4,
prefetchDistance = 4
)
var cinemaPagerState by mutableStateOf(CinemaPagerState())
private set
fun findCinemas() {
val pager = Pager(
config = pagingConfig,
pagingSourceFactory = {
cinemaRepository.getAllCinemasPaged()
}
)
cinemaPagerState = CinemaPagerState(pager.flow)
}
suspend fun deleteCinema(cinema: Cinema) {
cinemaRepository.deleteCinema(cinema)
}
}
data class CinemaPagerState(
val cinemaPagingData: Flow<PagingData<Cinema>> = emptyFlow()
)

View File

@ -0,0 +1,137 @@
package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Cinema
@Composable
fun CinemaView(
navController: NavController,
viewModel: CinemaViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val cinemaUiState by viewModel.cinemaUiState.collectAsState()
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
) {
val cinema: Cinema? = cinemaUiState.cinemaWithSessions?.cinema
if (cinema != null) {
Box(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.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)
)
}
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)
)
Text(
text = cinema.description,
color = MaterialTheme.colorScheme.onSecondary
)
}
}
}
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)
)
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(cinemaUiState.cinemaWithSessions!!, navController)
}
}
}

View File

@ -0,0 +1,31 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.CinemaWithSessions
import com.example.myapplication.database.entities.repository.CinemaRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class CinemaViewModel(
savedStateHandle: SavedStateHandle,
private val cinemaRepository: CinemaRepository
) : ViewModel() {
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
val cinemaUiState: StateFlow<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)

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.composeui package com.example.myapplication.database.entities.composeui
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -16,40 +16,32 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
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.collectAsState
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.getValue
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.platform.LocalContext
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.navigation.NavController import androidx.navigation.NavController
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.entities.model.Order
import com.example.myapplication.ui.theme.PmudemoTheme import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable @Composable
fun OrderList(navController: NavController?, userId: Int?) { fun OrderList(
val context = LocalContext.current navController: NavController?,
val orders = remember { mutableStateListOf<Order>() } userId: Int?,
LaunchedEffect(Unit) { viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory)
withContext(Dispatchers.IO) { ) {
AppDatabase.getInstance(context).orderDao().getAll(userId).collect { data -> val coroutineScope = rememberCoroutineScope()
orders.clear() val ordersUiState by viewModel.orderListUiState.collectAsState()
orders.addAll(data)
}
}
}
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(all = 10.dp) .padding(all = 10.dp)
) { ) {
items(orders) { order -> items(ordersUiState.orderList) { order ->
val orderId = Screen.OrderView.route.replace("{id}", order.uid.toString()) val orderId = Screen.OrderView.route.replace("{id}", order.uid.toString())
Box( Box(
modifier = Modifier modifier = Modifier

View File

@ -0,0 +1,25 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.repository.OrderRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class OrderListViewModel(
private val orderRepository: OrderRepository
) : ViewModel() {
val orderListUiState: StateFlow<OrderListUiState> = orderRepository.getAllOrders(1).map {
OrderListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = OrderListUiState()
)
}
data class OrderListUiState(val orderList: List<Order> = listOf())

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.composeui package com.example.myapplication.database.entities.composeui
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
@ -18,42 +18,33 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
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.collectAsState
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.getValue
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.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.draw.clip
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
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 com.example.myapplication.database.AppDatabase import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.entities.model.SessionFromOrder
import com.example.myapplication.ui.theme.PmudemoTheme import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
@Composable @Composable
fun OrderView(id: Int) { fun OrderView(
val context = LocalContext.current id: Int,
val sessions = remember { viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
mutableStateListOf<SessionFromOrder>() ) {
} val coroutineScope = rememberCoroutineScope()
LaunchedEffect(Unit) { val orderUiState by viewModel.orderUiState.collectAsState()
withContext(Dispatchers.IO) {
sessions.clear()
sessions.addAll(AppDatabase.getInstance(context).orderDao().getByUid(id))
}
}
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.padding(10.dp) .padding(10.dp)
) { ) {
items(sessions) { session -> items(orderUiState.sessionList) { session ->
val count = remember { mutableStateOf(session.count) } val count = remember { mutableStateOf(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)

View File

@ -0,0 +1,28 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
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.map
import kotlinx.coroutines.flow.stateIn
class OrderViewModel(
savedStateHandle: SavedStateHandle,
private val orderRepository: OrderRepository
) : ViewModel() {
private val orderUid: Int = checkNotNull(savedStateHandle["id"])
val orderUiState: StateFlow<OrderUiState> = orderRepository.getOrder(orderUid).map {
OrderUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = OrderUiState()
)
}
data class OrderUiState(val sessionList: List<SessionFromOrder> = listOf())

View File

@ -0,0 +1,141 @@
package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
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.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.CinemaWithSessions
import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun SessionList(
cinemaWithSessions: CinemaWithSessions,
navController: NavController,
viewModel: SessionListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
LazyColumn {
if (cinemaWithSessions.sessions.isEmpty()) {
item {
Text(
text = stringResource(R.string.Session_empty_description),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
}
} else {
items(cinemaWithSessions.sessions, key = { it.uid }) { session ->
val route = Screen.SessionEdit.route.replace(
"{id}", session.uid.toString()
).replace(
"{cinemaId}", cinemaWithSessions.cinema.uid.toString()
)
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
.padding(vertical = 7.dp)
.clickable {
navController.navigate(route)
}
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (cinemaWithSessions.cinema.image != null) Image(
bitmap = BitmapFactory.decodeByteArray(
cinemaWithSessions.cinema.image,
0,
cinemaWithSessions.cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "Цена: ${session.price}\n" + "Билетов: ${session.availableCount}",
color = MaterialTheme.colorScheme.onSecondary
)
}
IconButton(
onClick = {
coroutineScope.launch {
viewModel.addSessionInCart(sessionId = session.uid)
}
},
) {
Icon(
imageVector = Icons.Filled.ShoppingCart,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onSecondary
)
}
IconButton(
onClick = {
coroutineScope.launch {
viewModel.deleteSession(session = session)
}
},
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onSecondary
)
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCinema
import com.example.myapplication.database.entities.model.UserSessionCrossRef
import com.example.myapplication.database.entities.repository.SessionRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository
class SessionListViewModel(
private val sessionRepository: SessionRepository,
private val userSessionRepository: UserSessionRepository
) : ViewModel() {
suspend fun deleteSession(session: SessionFromCinema) {
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 {
userSessionRepository.insertUserSession(UserSessionCrossRef(1, sessionId, count))
} catch (_: Exception) {
}
}
}

View File

@ -1,4 +1,4 @@
package com.example.myapplication.user.composeui package com.example.myapplication.database.entities.composeui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -28,7 +28,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.navigation.NavController
import com.example.myapplication.datastore.DataStoreManager import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.datastore.SettingData import com.example.myapplication.datastore.SettingData
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -0,0 +1,127 @@
package com.example.myapplication.database.entities.composeui.edit
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
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.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.launch
@Composable
fun CinemaEdit(
navController: NavController,
viewModel: CinemaEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val coroutineScope = rememberCoroutineScope()
CinemaEdit(
cinemaUiState = viewModel.cinemaUiState,
onClick = {
coroutineScope.launch {
viewModel.saveCinema()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
)
}
@Composable
private fun CinemaEdit(
cinemaUiState: CinemaUiState,
onClick: () -> Unit,
onUpdate: (CinemaDetails) -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = cinemaUiState.cinemaDetails.name,
onValueChange = { onUpdate(cinemaUiState.cinemaDetails.copy(name = it)) },
label = { Text(stringResource(id = R.string.Cinema_name)) },
singleLine = true
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = cinemaUiState.cinemaDetails.description,
onValueChange = { onUpdate(cinemaUiState.cinemaDetails.copy(description = it)) },
label = { Text(stringResource(id = R.string.Cinema_description)) },
singleLine = true
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = cinemaUiState.cinemaDetails.year.toString(),
onValueChange = { newValue ->
val parsedYear = newValue.toLongOrNull() ?: 0L
onUpdate(cinemaUiState.cinemaDetails.copy(year = parsedYear))
},
label = { Text(stringResource(id = R.string.Cinema_year)) },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
visualTransformation = VisualTransformation.None // Отключает маскировку
)
if (cinemaUiState.cinemaDetails.image != null)
ImageUploader(
bitmap = BitmapFactory.decodeByteArray(
cinemaUiState.cinemaDetails.image,
0,
cinemaUiState.cinemaDetails.image.size
),
onResult = { byteArray: ByteArray? ->
onUpdate(
cinemaUiState.cinemaDetails.copy(
image = byteArray
)
)
}
)
Button(
onClick = onClick,
enabled = cinemaUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.Save_button))
}
}
}
@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 CinemaEditPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
CinemaEdit(
cinemaUiState = CinemaUiState(),
onClick = {},
onUpdate = {},
)
}
}
}

View File

@ -0,0 +1,101 @@
package com.example.myapplication.database.entities.composeui.edit
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.entities.model.Cinema
import com.example.myapplication.database.entities.model.CinemaWithSessions
import com.example.myapplication.database.entities.model.SessionFromCinema
import com.example.myapplication.database.entities.repository.CinemaRepository
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class CinemaEditViewModel(
savedStateHandle: SavedStateHandle,
private val cinemaRepository: CinemaRepository
) : ViewModel() {
var cinemaUiState by mutableStateOf(CinemaUiState())
private set
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (cinemaUid > 0) {
cinemaUiState = cinemaRepository.getCinema(cinemaUid)
.filterNotNull()
.first()
.toUiState(true)
}
}
}
fun updateUiState(cinemaDetails: CinemaDetails) {
cinemaUiState = CinemaUiState(
cinemaDetails = cinemaDetails,
isEntryValid = validateInput(cinemaDetails)
)
}
suspend fun saveCinema() {
if (validateInput()) {
if (cinemaUid > 0) {
cinemaRepository.updateCinema(cinemaUiState.cinemaDetails.toCinema(cinemaUid))
} else {
cinemaRepository.insertCinema(cinemaUiState.cinemaDetails.toCinema())
}
}
}
private fun validateInput(uiState: CinemaDetails = cinemaUiState.cinemaDetails): Boolean {
return with(uiState) {
name.isNotBlank()
&& description.isNotBlank()
&& year >= 1900
&& year <= 2100
}
}
}
data class CinemaUiState(
val cinemaDetails: CinemaDetails = CinemaDetails(),
val isEntryValid: Boolean = false
)
data class CinemaDetails(
val name: String = "",
val description: String = "",
val image: ByteArray? = byteArrayOf(),
val year: Long = 1900,
val sessions: List<SessionFromCinema> = emptyList()
)
fun CinemaDetails.toCinema(uid: Int = 0): Cinema = Cinema(
uid = uid,
name = name,
description = description,
image = image,
year = year
)
fun CinemaWithSessions.toDetails(): CinemaDetails {
val cinema = this.cinema
val sessions = this.sessions
return CinemaDetails(
name = cinema.name,
description = cinema.description,
image = cinema.image,
year = cinema.year,
sessions = sessions
)
}
fun CinemaWithSessions.toUiState(isEntryValid: Boolean = false): CinemaUiState = CinemaUiState(
cinemaDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,121 @@
package com.example.myapplication.database.entities.composeui.edit
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
import java.io.ByteArrayOutputStream
@Composable
fun ImageUploader(
bitmap: Bitmap?,
onResult: (ByteArray) -> Unit
) {
val context = LocalContext.current
val title: String = if (bitmap == null) {
stringResource(R.string.not_uploaded)
} else {
stringResource(R.string.size, bitmap.width, bitmap.height)
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri: Uri? ->
uri?.let {
val inputStream = context.contentResolver.openInputStream(uri)
val newBitmap: Bitmap = BitmapFactory.decodeStream(inputStream)
val scaledBitmap = resizeBitmapWithAspectRatio(newBitmap, 200)
val stream = ByteArrayOutputStream()
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
onResult(stream.toByteArray())
}
}
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(10.dp)
)
.aspectRatio(1f)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(10.dp)
)
.clickable { launcher.launch("image/*") }
.padding(16.dp)
) {
if (bitmap != null) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(shape = RoundedCornerShape(10.dp))
)
} else {
Image(
painter = painterResource(id = R.drawable.photo),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(shape = RoundedCornerShape(10.dp))
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = title,
color = MaterialTheme.colorScheme.onBackground
)
}
}
fun resizeBitmapWithAspectRatio(bitmap: Bitmap, maxHeight: Int): Bitmap {
if (bitmap.height <= maxHeight) {
return bitmap
}
val aspectRatio = bitmap.width.toFloat() / bitmap.height
val newWidth = (maxHeight * aspectRatio).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, maxHeight, true)
}

View File

@ -0,0 +1,150 @@
package com.example.myapplication.database.entities.composeui.edit
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TimePicker
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
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.database.entities.composeui.AppViewModelProvider
import kotlinx.coroutines.launch
import org.threeten.bp.Instant
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.ZoneId
import org.threeten.bp.ZoneOffset
@Composable
fun SessionEdit(
navController: NavController,
viewModel: SessionEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val coroutineScope = rememberCoroutineScope()
SessionEdit(
sessionUiState = viewModel.sessionUiState,
onClick = {
coroutineScope.launch {
viewModel.saveSession()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
fun Long.toLocalDate(): org.threeten.bp.LocalDate {
val instant = Instant.ofEpochMilli(this)
return instant.atZone(ZoneId.systemDefault()).toLocalDate()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SessionEdit(
sessionUiState: SessionUiState,
onClick: () -> Unit,
onUpdate: (SessionDetails) -> Unit,
) {
LazyColumn(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
) {
item {
if (sessionUiState.sessionDetails.dateTime != LocalDateTime.MIN) {
val selectedDateMillis =
sessionUiState.sessionDetails.dateTime.toInstant(ZoneOffset.UTC).toEpochMilli()
val dateState = rememberDatePickerState(
initialDisplayMode = DisplayMode.Input,
initialSelectedDateMillis = selectedDateMillis
)
val timeState = rememberTimePickerState(
sessionUiState.sessionDetails.dateTime.hour,
sessionUiState.sessionDetails.dateTime.minute
)
DatePicker(
state = dateState,
modifier = Modifier.padding(16.dp)
)
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
TimePicker(state = timeState)
}
val selectedDate = dateState.selectedDateMillis?.toLocalDate()
val selectedTime = LocalTime.of(timeState.hour, timeState.minute)
if (selectedDate != null) {
val resultDateTime = LocalDateTime.of(selectedDate, selectedTime)
onUpdate(sessionUiState.sessionDetails.copy(dateTime = resultDateTime))
}
} else {
val dateState = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val timeState = rememberTimePickerState()
DatePicker(
state = dateState,
modifier = Modifier.padding(16.dp)
)
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
TimePicker(state = timeState)
}
val selectedDate = dateState.selectedDateMillis?.toLocalDate()
val selectedTime = LocalTime.of(timeState.hour, timeState.minute)
if (selectedDate != null) {
val resultDateTime = LocalDateTime.of(selectedDate, selectedTime)
onUpdate(sessionUiState.sessionDetails.copy(dateTime = resultDateTime))
}
}
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = sessionUiState.sessionDetails.price,
label = { Text(text = "Цена") },
onValueChange = {
onUpdate(sessionUiState.sessionDetails.copy(price = it))
},
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = sessionUiState.sessionDetails.maxCount.toString(),
onValueChange = { newValue ->
val parsedMaxCount = newValue.toIntOrNull() ?: 0 // Преобразование в Int
onUpdate(sessionUiState.sessionDetails.copy(maxCount = parsedMaxCount))
},
label = { Text(text = "Количество") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
Button(
onClick = onClick,
enabled = sessionUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.Save_button))
}
}
}
}

View File

@ -0,0 +1,110 @@
package com.example.myapplication.database.entities.composeui.edit
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.entities.model.Session
import com.example.myapplication.database.entities.repository.SessionRepository
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.threeten.bp.LocalDateTime
class SessionEditViewModel(
savedStateHandle: SavedStateHandle,
private val sessionRepository: SessionRepository
) : ViewModel() {
var sessionUiState by mutableStateOf(SessionUiState())
private set
private val sessionUid: Int = checkNotNull(savedStateHandle["id"])
private val cinemaUid: Int = checkNotNull(savedStateHandle["cinemaId"])
init {
viewModelScope.launch {
if (sessionUid > 0) {
sessionUiState = sessionRepository.getSession(sessionUid)
.filterNotNull()
.first()
.toUiState(true)
}
}
}
fun updateUiState(sessionDetails: SessionDetails) {
sessionUiState = SessionUiState(
sessionDetails = sessionDetails,
isEntryValid = validateInput(sessionDetails)
)
}
suspend fun saveSession() {
if (validateInput()) {
if (cinemaUid > 0)
if (sessionUid > 0) {
sessionRepository.updateSession(
sessionUiState.sessionDetails
.toSession(uid = sessionUid, cinemaUid = cinemaUid)
)
} else {
sessionRepository.insertSession(
sessionUiState.sessionDetails.toSession(
cinemaUid = cinemaUid
)
)
}
}
}
private fun validateInput(uiState: SessionDetails = sessionUiState.sessionDetails): Boolean {
return with(uiState) {
dateTime != LocalDateTime.MIN
&& isValidDouble(price)
&& maxCount > 0
&& cinemaUid > 0
}
}
}
val regex = """^-?\d+(.\d+)?+(,\d+)?$""".toRegex()
fun isValidDouble(input: String): Boolean {
return regex.matches(input)
}
data class SessionUiState(
val sessionDetails: SessionDetails = SessionDetails(),
val isEntryValid: Boolean = false
)
data class SessionDetails(
val uid: Int = 0,
val dateTime: LocalDateTime = LocalDateTime.MIN,
val price: String = "0",
val maxCount: Int = 0,
val cinemaId: Int = 0
)
fun SessionDetails.toSession(uid: Int = 0, cinemaUid: Int = 0): Session = Session(
uid = uid,
dateTime = dateTime,
price = price.toDoubleOrNull() ?: 0.0,
maxCount = maxCount,
cinemaId = cinemaUid
)
fun Session.toDetails(): SessionDetails = SessionDetails(
uid = uid,
dateTime = dateTime,
price = price.toString(),
maxCount = maxCount,
cinemaId = cinemaId
)
fun Session.toUiState(isEntryValid: Boolean = false): SessionUiState = SessionUiState(
sessionDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,39 @@
package com.example.myapplication.database.entities.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.SessionFromCinema
import kotlinx.coroutines.flow.Flow
@Dao
interface CinemaDao {
@Query("select * from cinemas order by name")
fun getAll(): Flow<List<Cinema>>
@Query("select * from cinemas order by name")
fun getAllCinemasPaged(): PagingSource<Int, Cinema>
@Query(
"SELECT c.*, s.uid as session_uid, s.date_time, s.price, s.max_count-IFNULL(SUM(os.count), 0) as available_count " +
"FROM cinemas AS c " +
"LEFT JOIN sessions AS s ON s.cinema_id = c.uid " +
"LEFT JOIN orders_sessions AS os ON os.session_id = s.uid " +
"WHERE c.uid = :cinemaId " +
"GROUP BY session_uid"
)
fun getByUid(cinemaId: Int?): Flow<Map<Cinema, List<SessionFromCinema>>>
@Insert
suspend fun insert(cinema: Cinema)
@Update
suspend fun update(cinema: Cinema)
@Delete
suspend fun delete(cinema: Cinema)
}

View File

@ -0,0 +1,34 @@
package com.example.myapplication.database.entities.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.SessionFromOrder
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Query("select * from orders where user_id = :userId")
fun getAll(userId: Int?): Flow<List<Order>>
@Query(
"SELECT o.*, s.*, os.count, os.frozen_price " +
"FROM orders AS o " +
"JOIN orders_sessions AS os ON os.order_id = o.uid " +
"JOIN sessions AS s ON s.uid = os.session_id " +
"WHERE o.uid = :orderId"
)
fun getByUid(orderId: Int?): Flow<List<SessionFromOrder>>
@Insert
suspend fun insert(order: Order): Long
@Update
suspend fun update(order: Order)
@Delete
suspend fun delete(order: Order)
}

View File

@ -1,11 +1,10 @@
package com.example.myapplication.entities.dao package com.example.myapplication.database.entities.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.example.myapplication.entities.model.OrderSessionCrossRef import com.example.myapplication.database.entities.model.OrderSessionCrossRef
@Dao @Dao
interface OrderSessionCrossRefDao { interface OrderSessionCrossRefDao {

View File

@ -1,13 +1,18 @@
package com.example.myapplication.entities.dao package com.example.myapplication.database.entities.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.example.myapplication.entities.model.Session import com.example.myapplication.database.entities.model.Session
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface SessionDao { interface SessionDao {
@Query("select * from sessions where sessions.uid = :uid")
fun getByUid(uid: Int): Flow<Session>
@Insert @Insert
suspend fun insert(session: Session) suspend fun insert(session: Session)

View File

@ -1,12 +1,12 @@
package com.example.myapplication.entities.dao package com.example.myapplication.database.entities.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.example.myapplication.entities.model.SessionFromCart import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.entities.model.User import com.example.myapplication.database.entities.model.User
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@ -15,12 +15,12 @@ interface UserDao {
fun getAll(): Flow<List<User>> fun getAll(): Flow<List<User>>
@Query( @Query(
"SELECT sessions.*, sessions.max_count-sum(orders_sessions.count) 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 " +
"join users_sessions on sessions.uid = users_sessions.session_id " + "join users_sessions on sessions.uid = users_sessions.session_id " +
"join orders_sessions on sessions.uid = orders_sessions.session_id " + "left join orders_sessions on sessions.uid = orders_sessions.session_id " +
"where users_sessions.user_id = :userId " + "where users_sessions.user_id = :userId " +
"GROUP BY orders_sessions.session_id " "GROUP BY users_sessions.session_id "
) )
fun getCartByUid(userId: Int): Flow<List<SessionFromCart>> fun getCartByUid(userId: Int): Flow<List<SessionFromCart>>

View File

@ -1,13 +1,11 @@
package com.example.myapplication.entities.dao package com.example.myapplication.database.entities.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.example.myapplication.entities.model.SessionFromCart import com.example.myapplication.database.entities.model.UserSessionCrossRef
import com.example.myapplication.entities.model.UserSessionCrossRef
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface UserSessionCrossRefDao { interface UserSessionCrossRefDao {
@ -19,4 +17,7 @@ interface UserSessionCrossRefDao {
@Delete @Delete
suspend fun delete(userSessionCrossRef: UserSessionCrossRef) suspend fun delete(userSessionCrossRef: UserSessionCrossRef)
@Query("DELETE FROM users_sessions where users_sessions.user_id = :userId")
suspend fun deleteByUserUid(userId: Int)
} }

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
@ -8,7 +8,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "cinemas") @Entity(tableName = "cinemas")
data class Cinema( data class Cinema(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
val name: String, val name: String,
val description: String, val description: String,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB) @ColumnInfo(typeAffinity = ColumnInfo.BLOB)
@ -21,18 +21,37 @@ data class Cinema(
description: String, description: String,
image: ByteArray?, image: ByteArray?,
year: Long year: Long
) : this(null, name, description, image, year) ) : this(0, name, description, image, year)
companion object {
fun getCinema(index: Int = 0): Cinema {
return Cinema(
index,
"name",
"desc",
byteArrayOf(),
0,
)
}
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Cinema other as Cinema
if (uid != other.uid) return false if (uid != other.uid) return false
if (name != other.name) return false
if (description != other.description) return false
if (year != other.year) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid ?: -1 var result = uid
result = 31 * result + name.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + year.hashCode()
return result
} }
} }

View File

@ -0,0 +1,24 @@
package com.example.myapplication.database.entities.model
data class CinemaWithSessions(
val cinema: Cinema,
val sessions: List<SessionFromCinema>
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CinemaWithSessions
if (cinema != other.cinema) return false
if (sessions != other.sessions) return false
return true
}
override fun hashCode(): Int {
var result = cinema.hashCode()
result = 31 * result + sessions.hashCode()
return result
}
}

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.TypeConverter import androidx.room.TypeConverter
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
@ -13,21 +13,21 @@ import org.threeten.bp.LocalDateTime
entity = Cinema::class, entity = Cinema::class,
parentColumns = ["uid"], parentColumns = ["uid"],
childColumns = ["cinema_id"], childColumns = ["cinema_id"],
onDelete = ForeignKey.RESTRICT, onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.RESTRICT onUpdate = ForeignKey.RESTRICT
) )
] ]
) )
data class Session( data class Session(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "date_time") @ColumnInfo(name = "date_time")
val dateTime: LocalDateTime, val dateTime: LocalDateTime,
val price: Double, val price: Double,
@ColumnInfo(name = "max_count") @ColumnInfo(name = "max_count")
val maxCount: Int, val maxCount: Int,
@ColumnInfo(name = "cinema_id", index = true) @ColumnInfo(name = "cinema_id", index = true)
val cinemaId: Int?, val cinemaId: Int = 0,
) { ) {
@Ignore @Ignore
constructor( constructor(
@ -35,18 +35,39 @@ data class Session(
price: Double, price: Double,
maxCount: Int, maxCount: Int,
cinema: Cinema, cinema: Cinema,
) : this(null, dateTime, price, maxCount, cinema.uid) ) : this(0, dateTime, price, maxCount, cinema.uid)
companion object {
fun getSession(index: Int = 0): Session {
return Session(
index,
LocalDateTime.MIN,
0.0,
0,
0
)
}
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Session other as Session
if (uid != other.uid) return false if (uid != other.uid) return false
if (dateTime != other.dateTime) return false
if (price != other.price) return false
if (maxCount != other.maxCount) return false
if (cinemaId != other.cinemaId) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid ?: -1 var result = uid
result = 31 * result + dateTime.hashCode()
result = 31 * result + price.hashCode()
result = 31 * result + maxCount.hashCode()
result = 31 * result + cinemaId.hashCode()
return result
} }
} }

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Relation import androidx.room.Relation
@ -6,7 +6,7 @@ import org.threeten.bp.LocalDateTime
data class SessionFromCart( data class SessionFromCart(
@ColumnInfo(name = "uid") @ColumnInfo(name = "uid")
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "date_time") @ColumnInfo(name = "date_time")
val dateTime: LocalDateTime, val dateTime: LocalDateTime,
val price: Double, val price: Double,
@ -14,7 +14,7 @@ data class SessionFromCart(
val availableCount: Int, val availableCount: Int,
val count: Int, val count: Int,
@ColumnInfo(name = "cinema_id") @ColumnInfo(name = "cinema_id")
val cinemaId: Int?, val cinemaId: Int = 0,
@Relation( @Relation(
parentColumn = "cinema_id", parentColumn = "cinema_id",
entity = Cinema::class, entity = Cinema::class,

View File

@ -0,0 +1,35 @@
package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo
import org.threeten.bp.LocalDateTime
import org.threeten.bp.format.DateTimeFormatter
data class SessionFromCinema(
@ColumnInfo(name = "session_uid")
val uid: Int,
@ColumnInfo(name = "date_time")
val dateTime: LocalDateTime,
val price: Double,
@ColumnInfo(name = "available_count")
val availableCount: Int,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SessionFromCinema
if (uid != other.uid) return false
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
if (dateFormatter.format(dateTime) != dateFormatter.format(other.dateTime)) return false
if (price != other.price) return false
if (availableCount != other.availableCount) return false
return true
}
override fun hashCode(): Int {
var result = uid
result = 31 * result + dateTime.hashCode()
result = 31 * result + price.hashCode()
result = 31 * result + availableCount.hashCode()
return result
}
}

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Relation import androidx.room.Relation

View File

@ -1,4 +1,4 @@
package com.example.myapplication.entities.model package com.example.myapplication.database.entities.model
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -6,7 +6,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "users") @Entity(tableName = "users")
data class User( data class User(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
val login: String, val login: String,
val password: String val password: String
) { ) {

View File

@ -0,0 +1,36 @@
package com.example.myapplication.database.entities.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import java.util.Objects.hash
@Entity(
tableName = "users_sessions",
primaryKeys = ["user_id", "session_id"]
)
data class UserSessionCrossRef(
@ColumnInfo(name = "user_id", index = true)
val userId: Int,
@ColumnInfo(name = "session_id", index = true)
val sessionId: Int,
@ColumnInfo(name = "count")
val count: Int,
) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (javaClass != other?.javaClass) {
return false
}
other as UserSessionCrossRef
if (userId == other.userId && sessionId == other.sessionId) {
return true
}
return false
}
override fun hashCode(): Int {
return hash(userId, sessionId)
}
}

View File

@ -0,0 +1,15 @@
package com.example.myapplication.database.entities.repository
import androidx.paging.PagingSource
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.CinemaWithSessions
import kotlinx.coroutines.flow.Flow
interface CinemaRepository {
fun getAllCinemas(): Flow<List<Cinema>>
fun getAllCinemasPaged(): PagingSource<Int, Cinema>
fun getCinema(uid: Int): Flow<CinemaWithSessions>
suspend fun insertCinema(cinema: Cinema)
suspend fun updateCinema(cinema: Cinema)
suspend fun deleteCinema(cinema: Cinema)
}

View File

@ -0,0 +1,33 @@
package com.example.myapplication.database.entities.repository
import androidx.paging.PagingSource
import com.example.myapplication.database.entities.dao.CinemaDao
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.CinemaWithSessions
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class OfflineCinemaRepository(private val cinemaDao: CinemaDao) : CinemaRepository {
override fun getAllCinemas(): Flow<List<Cinema>> = cinemaDao.getAll()
override fun getAllCinemasPaged(): PagingSource<Int, Cinema> = cinemaDao.getAllCinemasPaged()
override fun getCinema(uid: Int): Flow<CinemaWithSessions> {
return flow {
cinemaDao.getByUid(uid).collect {
emit(it.firstNotNullOf {
CinemaWithSessions(
cinema = it.key,
sessions = it.value
)
})
}
}
}
override suspend fun insertCinema(cinema: Cinema) = cinemaDao.insert(cinema)
override suspend fun updateCinema(cinema: Cinema) = cinemaDao.update(cinema)
override suspend fun deleteCinema(cinema: Cinema) = cinemaDao.delete(cinema)
}

View File

@ -0,0 +1,18 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.dao.OrderDao
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.SessionFromOrder
import kotlinx.coroutines.flow.Flow
class OfflineOrderRepository(private val orderDao: OrderDao) : OrderRepository {
override fun getAllOrders(userId: Int?): Flow<List<Order>> = orderDao.getAll(userId)
override fun getOrder(orderId: Int?): Flow<List<SessionFromOrder>> = orderDao.getByUid(orderId)
override suspend fun insertOrder(order: Order): Long = orderDao.insert(order)
override suspend fun updateOrder(order: Order) = orderDao.update(order)
override suspend fun deleteOrder(order: Order) = orderDao.delete(order)
}

View File

@ -0,0 +1,16 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.dao.OrderSessionCrossRefDao
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
class OfflineOrderSessionRepository(private val orderSessionDao: OrderSessionCrossRefDao) :
OrderSessionRepository {
override suspend fun insertOrderSession(orderSessionCrossRef: OrderSessionCrossRef) =
orderSessionDao.insert(orderSessionCrossRef)
override suspend fun updateOrderSession(orderSessionCrossRef: OrderSessionCrossRef) =
orderSessionDao.update(orderSessionCrossRef)
override suspend fun deleteOrderSession(orderSessionCrossRef: OrderSessionCrossRef) =
orderSessionDao.delete(orderSessionCrossRef)
}

View File

@ -0,0 +1,15 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.dao.SessionDao
import com.example.myapplication.database.entities.model.Session
import kotlinx.coroutines.flow.Flow
class OfflineSessionRepository(private val sessionDao: SessionDao) : SessionRepository {
override fun getSession(uid: Int): Flow<Session?> = sessionDao.getByUid(uid)
override suspend fun insertSession(session: Session) = sessionDao.insert(session)
override suspend fun updateSession(session: Session) = sessionDao.update(session)
override suspend fun deleteSession(session: Session) = sessionDao.delete(session)
}

View File

@ -0,0 +1,19 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.dao.UserDao
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.User
import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override fun getCartByUser(userId: Int): Flow<List<SessionFromCart>> =
userDao.getCartByUid(userId)
override suspend fun insertUser(user: User) = userDao.insert(user)
override suspend fun updateUser(user: User) = userDao.update(user)
override suspend fun deleteUser(user: User) = userDao.delete(user)
}

View File

@ -0,0 +1,18 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.dao.UserSessionCrossRefDao
import com.example.myapplication.database.entities.model.UserSessionCrossRef
class OfflineUserSessionRepository(private val userSessionDao: UserSessionCrossRefDao) :
UserSessionRepository {
override suspend fun insertUserSession(userSessionCrossRef: UserSessionCrossRef) =
userSessionDao.insert(userSessionCrossRef)
override suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef) =
userSessionDao.update(userSessionCrossRef)
override suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef) =
userSessionDao.delete(userSessionCrossRef)
override suspend fun deleteUserSessions(userId: Int) = userSessionDao.deleteByUserUid(userId)
}

View File

@ -0,0 +1,13 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.SessionFromOrder
import kotlinx.coroutines.flow.Flow
interface OrderRepository {
fun getAllOrders(userId: Int?): Flow<List<Order>>
fun getOrder(orderId: Int?): Flow<List<SessionFromOrder>>
suspend fun insertOrder(order: Order): Long
suspend fun updateOrder(order: Order)
suspend fun deleteOrder(order: Order)
}

View File

@ -0,0 +1,9 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
interface OrderSessionRepository {
suspend fun insertOrderSession(orderSessionCrossRef: OrderSessionCrossRef)
suspend fun updateOrderSession(orderSessionCrossRef: OrderSessionCrossRef)
suspend fun deleteOrderSession(orderSessionCrossRef: OrderSessionCrossRef)
}

View File

@ -0,0 +1,11 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.model.Session
import kotlinx.coroutines.flow.Flow
interface SessionRepository {
fun getSession(uid: Int): Flow<Session?>
suspend fun insertSession(session: Session)
suspend fun updateSession(session: Session)
suspend fun deleteSession(session: Session)
}

View File

@ -0,0 +1,13 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.User
import kotlinx.coroutines.flow.Flow
interface UserRepository {
fun getAllUsers(): Flow<List<User>>
fun getCartByUser(userId: Int): Flow<List<SessionFromCart>>
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
}

View File

@ -0,0 +1,10 @@
package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.model.UserSessionCrossRef
interface UserSessionRepository {
suspend fun insertUserSession(userSessionCrossRef: UserSessionCrossRef)
suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef)
suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef)
suspend fun deleteUserSessions(userId: Int)
}

View File

@ -1,109 +0,0 @@
package com.example.myapplication.entities.composeui
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.entities.model.Cinema
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun CinemaList(navController: NavController?) {
val context = LocalContext.current
val cinemas = remember { mutableStateListOf<Cinema>() }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).cinemaDao().getAll().collect { data ->
cinemas.clear()
cinemas.addAll(data)
}
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
) {
items(cinemas) { cinema ->
val cinemaId = Screen.CinemaView.route.replace("{id}", cinema.uid.toString())
Box(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.clickable { navController?.navigate(cinemaId) }
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (cinema.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
cinema.image,
0,
cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Text(
"${cinema.name}, ${cinema.year}",
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 CinemaListPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
CinemaList(navController = null)
}
}
}

View File

@ -1,209 +0,0 @@
package com.example.myapplication.entities.composeui
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.entities.model.Cinema
import com.example.myapplication.entities.model.SessionFromCinema
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun CinemaView(id: Int) {
val context = LocalContext.current
val cinemaWithSessions = remember {
mutableStateListOf<Pair<Cinema, List<SessionFromCinema>>>()
}
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
cinemaWithSessions.clear()
cinemaWithSessions
.addAll(AppDatabase.getInstance(context).cinemaDao().getByUid(id).map { (cinema, sessionFromCinema) ->
Pair(cinema, sessionFromCinema)
})
}
}
val cinema = cinemaWithSessions.firstOrNull()?.first
val sessions = cinemaWithSessions.firstOrNull()?.second
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
item {
Box(
modifier = Modifier
.fillMaxSize()
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(color = MaterialTheme.colorScheme.secondary),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "${cinema?.name ?: ""}, ${cinema?.year ?: 1930}",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onSecondary
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
)
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)
)
Text(
text = cinema?.description ?: "",
color = MaterialTheme.colorScheme.onSecondary
)
}
}
}
item {
Text(
text = "Сеансы",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onBackground
),
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, bottom = 8.dp),
)
}
if (sessions != null) {
items(sessions) { session ->
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formattedDate = dateFormatter.format(session.dateTime)
Text(
text = formattedDate,
color = MaterialTheme.colorScheme.onBackground,
)
Box(
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 (cinema?.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
cinema.image,
0,
cinema.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "Цена: ${session.price}\n" +
"Билетов: ${session.availableCount}",
color = MaterialTheme.colorScheme.onSecondary
)
}
}
Icon(
imageVector = Icons.Filled.ShoppingCart,
contentDescription = null,
modifier = Modifier
.padding(10.dp)
.size(24.dp)
.clickable {}
.align(Alignment.CenterEnd),
tint = 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 CinemaViewPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
CinemaView(id = 0)
}
}
}

View File

@ -1,33 +0,0 @@
package com.example.myapplication.entities.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.entities.model.Cinema
import com.example.myapplication.entities.model.SessionFromCinema
import kotlinx.coroutines.flow.Flow
@Dao
interface CinemaDao {
@Query("select * from cinemas order by name")
fun getAll(): Flow<List<Cinema>>
@Query("SELECT c.*, s.date_time, s.price, s.max_count-IFNULL(SUM(os.count), 0) as available_count " +
"FROM cinemas AS c " +
"JOIN sessions AS s ON s.cinema_id = c.uid " +
"LEFT JOIN orders_sessions AS os ON os.session_id = s.uid " +
"WHERE c.uid = :cinemaId " +
"GROUP BY os.session_id")
suspend fun getByUid(cinemaId: Int?): Map<Cinema, List<SessionFromCinema>>
@Insert
suspend fun insert(cinema: Cinema)
@Update
suspend fun update(cinema: Cinema)
@Delete
suspend fun delete(cinema: Cinema)
}

View File

@ -1,32 +0,0 @@
package com.example.myapplication.entities.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.entities.model.Order
import com.example.myapplication.entities.model.SessionFromOrder
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Query("select * from orders where user_id = :userId")
fun getAll(userId: Int?): Flow<List<Order>>
@Query("SELECT o.*, s.*, os.count, os.frozen_price " +
"FROM orders AS o " +
"JOIN orders_sessions AS os ON os.order_id = o.uid " +
"JOIN sessions AS s ON s.uid = os.session_id " +
"WHERE o.uid = :orderId")
fun getByUid(orderId: Int?): List<SessionFromOrder>
@Insert
suspend fun insert(order: Order)
@Update
suspend fun update(order: Order)
@Delete
suspend fun delete(order: Order)
}

View File

@ -1,14 +0,0 @@
package com.example.myapplication.entities.model
import androidx.room.ColumnInfo
import org.threeten.bp.LocalDateTime
data class SessionFromCinema (
@ColumnInfo(name = "uid")
val uid: Int?,
@ColumnInfo(name = "date_time")
val dateTime: LocalDateTime,
val price: Double,
@ColumnInfo(name = "available_count")
val availableCount: Int,
)

View File

@ -1,17 +0,0 @@
package com.example.myapplication.entities.model
import androidx.room.ColumnInfo
import androidx.room.Entity
@Entity(
tableName = "users_sessions",
primaryKeys = ["user_id", "session_id"]
)
data class UserSessionCrossRef(
@ColumnInfo(name = "user_id", index = true)
val userId: Int,
@ColumnInfo(name = "session_id", index = true)
val sessionId: Int,
@ColumnInfo(name = "count")
val count: Int,
)

View File

@ -3,6 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#000000" <path
android:fillColor="#000000"
android:pathData="M19,13H5v-2h14v2z" /> android:pathData="M19,13H5v-2h14v2z" />
</vector> </vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,25 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<!-- Зона фона -->
<rect width="64" height="64" rx="8" ry="8" fill="#FFC107" />
<!-- Билет -->
<rect x="8" y="12" width="48" height="40" rx="4" ry="4" fill="#FFFFFF" />
<!-- Линия для разделения -->
<line x1="8" y1="32" x2="56" y2="32" stroke="#FFA000" stroke-width="2" />
<!-- Линия для номера билета -->
<line x1="16" y1="48" x2="48" y2="48" stroke="#FFA000" stroke-width="2" />
<!-- Текст для номера билета -->
<text x="32" y="53" fill="#FFA000" font-size="12" text-anchor="middle">
</text>
</svg>
</vector>

View File

@ -2,6 +2,7 @@
<string name="app_name">pmu-demo</string> <string name="app_name">pmu-demo</string>
<string name="Cinema_main_title">Фильмы</string> <string name="Cinema_main_title">Фильмы</string>
<string name="Cinema_view_title">Фильм</string> <string name="Cinema_view_title">Фильм</string>
<string name="Session_view_title">Сеанс</string>
<string name="Order_view_title">Заказ</string> <string name="Order_view_title">Заказ</string>
<string name="Cinema_name">Название</string> <string name="Cinema_name">Название</string>
<string name="Cinema_year">Год</string> <string name="Cinema_year">Год</string>
@ -11,4 +12,11 @@
<string name="Order_title">Мои заказы</string> <string name="Order_title">Мои заказы</string>
<string name="Profile_title">Профиль</string> <string name="Profile_title">Профиль</string>
<string name="Sessions_title">Сеансы</string> <string name="Sessions_title">Сеансы</string>
<string name="Session_dateTime">Время</string>
<string name="Save_button">Сохранить</string>
<string name="Cinema_empty_description">Записи о фильмах отсутствуют</string>
<string name="Session_empty_description">Записи о сеансах отсутствуют</string>
<string name="session_cinema_not_select">Фильм не указан</string>
<string name="size">Размер загруженного изображения: %1$dx%2$d</string>
<string name="not_uploaded">Загрузите изображение</string>
</resources> </resources>

View File

@ -1,9 +1,8 @@
package com.example.myapplication package com.example.myapplication
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.Assert.*
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
* *