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 {
applicationId = "com.example.myapplication"
minSdk = 24
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0"
@ -31,6 +31,7 @@ android {
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
@ -51,8 +52,16 @@ android {
}
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("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
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
@ -64,7 +73,7 @@ dependencies {
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material3:material3:1.1.2")
// Room
val room_version = "2.5.2"

View File

@ -1,13 +1,11 @@
package com.example.myapplication
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* 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">
<application
android:name=".CinemaApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_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.datastore.DataStoreManager
import com.example.myapplication.ui.theme.PmudemoTheme
import com.jakewharton.threetenabp.AndroidThreeTen
//import com.jakewharton.threetenabp.AndroidThreeTen
class MainComposeActivity : ComponentActivity() {
private val dataStoreManager = DataStoreManager(this)
@ -20,7 +21,7 @@ class MainComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
application.deleteDatabase("pmy-db")
AndroidThreeTen.init(this)
//AndroidThreeTen.init(this)
setContent {
PmudemoTheme(darkTheme = isDarkTheme.value) {
LaunchedEffect(key1 = true) {

View File

@ -2,6 +2,8 @@ package com.example.myapplication.composeui
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.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
@ -17,69 +20,202 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Delete
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.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.SwipeToDismiss
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.R
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.entities.model.SessionFromCart
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.database.entities.composeui.CartUiState
import com.example.myapplication.database.entities.composeui.CartViewModel
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun Cart(id: Int) {
val context = LocalContext.current
val sessions = remember { mutableStateListOf<SessionFromCart>() }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).userDao().getCartByUid(id).collect { data ->
sessions.clear()
sessions.addAll(data)
fun Cart(
viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val cartUiState by viewModel.cartUiState.collectAsState()
Cart(
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(
modifier = Modifier
.padding(all = 10.dp)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
session: SessionFromCart,
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
) {
SwipeToDismiss(
state = dismissState,
directions = setOf(
DismissDirection.EndToStart
),
background = {
val backgroundColor by animateColorAsState(
when (dismissState.targetValue) {
DismissValue.DismissedToStart -> Color.Red.copy(alpha = 0.8f)
else -> MaterialTheme.colorScheme.background
},
label = ""
)
val iconScale by animateFloatAsState(
targetValue = if (dismissState.targetValue == DismissValue.DismissedToStart) 1.3f else 0.5f,
label = ""
)
Box(
Modifier
.fillMaxSize()
.background(color = backgroundColor)
.padding(end = 16.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
modifier = Modifier.scale(iconScale),
imageVector = Icons.Outlined.Delete,
contentDescription = "Delete",
tint = Color.White
)
}
},
dismissContent = {
SessionListItem(
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) }
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
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondary)
modifier = modifier
) {
Row(
modifier = Modifier
@ -124,11 +260,7 @@ fun Cart(id: Int) {
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = {
if (currentCount > 0) {
currentCount--
}
}
onClick = { onChangeCount(session, 1, --currentCount) }
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.minus),
@ -144,11 +276,7 @@ fun Cart(id: Int) {
)
IconButton(
onClick = {
if (currentCount < session.availableCount) {
currentCount++
}
}
onClick = { onChangeCount(session, 1, ++currentCount) }
) {
Icon(
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 = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -184,7 +300,7 @@ fun CartPreview() {
Surface(
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.navArgument
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.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
fun Topbar(
@ -175,13 +177,26 @@ fun Navhost(
) {
composable(Screen.CinemaList.route) { CinemaList(navController) }
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.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(
Screen.CinemaView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { CinemaView(it.getInt("id")) }
backStackEntry.arguments?.let { CinemaView(navController) }
}
composable(
Screen.OrderView.route,

View File

@ -18,6 +18,15 @@ enum class Screen(
CinemaList(
"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(
"Session-list", R.string.Sessions_title, showInBottomBar = false
),
@ -27,9 +36,6 @@ enum class Screen(
OrderList(
"Order-list", R.string.Order_title, Icons.Filled.List
),
CinemaView(
"Cinema-view/{id}", R.string.Cinema_view_title, showInBottomBar = false
),
OrderView(
"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.graphics.Bitmap
import android.graphics.Color
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.entities.dao.CinemaDao
import com.example.myapplication.entities.dao.OrderDao
import com.example.myapplication.entities.dao.OrderSessionCrossRefDao
import com.example.myapplication.entities.dao.SessionDao
import com.example.myapplication.entities.dao.UserDao
import com.example.myapplication.entities.dao.UserSessionCrossRefDao
import com.example.myapplication.entities.model.Cinema
import com.example.myapplication.entities.model.LocalDateTimeConverter
import com.example.myapplication.entities.model.Order
import com.example.myapplication.entities.model.OrderSessionCrossRef
import com.example.myapplication.entities.model.Session
import com.example.myapplication.entities.model.User
import com.example.myapplication.entities.model.UserSessionCrossRef
import com.example.myapplication.database.entities.dao.CinemaDao
import com.example.myapplication.database.entities.dao.OrderDao
import com.example.myapplication.database.entities.dao.OrderSessionCrossRefDao
import com.example.myapplication.database.entities.dao.SessionDao
import com.example.myapplication.database.entities.dao.UserDao
import com.example.myapplication.database.entities.dao.UserSessionCrossRefDao
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.LocalDateTimeConverter
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.User
import com.example.myapplication.database.entities.model.UserSessionCrossRef
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -58,12 +57,30 @@ abstract class AppDatabase : RoomDatabase() {
userDao.insert(user2)
// Cinemas
val cinemaDao = database.cinemaDao()
val cinema1 = Cinema(1, "BLUE 1", "Desc1", createColoredImage(Color.BLUE), 2023)
val cinema2 = Cinema(2, "GREEN 2", "Desc2", createColoredImage(Color.GREEN), 2023)
val cinema3 = Cinema(3, "RED 3", "Desc3", createColoredImage(Color.RED), 2023)
val cinema1 =
Cinema(1, "a", "Desc1", createColoredImage(android.graphics.Color.BLUE), 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(cinema2)
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
val orderDao = database.orderDao()
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 session2 = Session(2, LocalDateTime.now(), 200.0, 110, cinema2.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(session2)
sessionDao.insert(session3)
sessionDao.insert(session4)
// OrderSessionCrossRef для связи заказов с сеансами
val orderSessionCrossRefDao = database.orderSessionCrossRefDao()
if (session1.uid != null && session2.uid != null && session3.uid != null && session4.uid != null) {
val orderSessionCrossRef1 = OrderSessionCrossRef(order1.uid, session3.uid, 150.0, 5)
val orderSessionCrossRef2 = OrderSessionCrossRef(order1.uid, session2.uid, 300.0, 10)
val orderSessionCrossRef3 = OrderSessionCrossRef(order2.uid, session2.uid, 350.0, 6)
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)
//val orderSessionCrossRef7 = OrderSessionCrossRef(order4.uid, session4.uid, 110.0, 1)
if (session1.uid != null && session2.uid != null && session3.uid != null) {
val orderSessionCrossRef1 =
OrderSessionCrossRef(order1.uid, session3.uid, 150.0, 5)
val orderSessionCrossRef2 =
OrderSessionCrossRef(order1.uid, session2.uid, 300.0, 10)
val orderSessionCrossRef3 =
OrderSessionCrossRef(order2.uid, session2.uid, 350.0, 6)
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(orderSessionCrossRef2)
orderSessionCrossRefDao.insert(orderSessionCrossRef3)
orderSessionCrossRefDao.insert(orderSessionCrossRef4)
orderSessionCrossRefDao.insert(orderSessionCrossRef5)
orderSessionCrossRefDao.insert(orderSessionCrossRef6)
//orderSessionCrossRefDao.insert(orderSessionCrossRef7)
}
// UserSessions
val userSessionCrossRefDao = database.userSessionCrossRefDao()
@ -143,5 +164,25 @@ abstract class AppDatabase : RoomDatabase() {
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 androidx.compose.foundation.background
@ -16,40 +16,32 @@ 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.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
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.composeui.navigation.Screen
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.entities.model.Order
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun OrderList(navController: NavController?, userId: Int?) {
val context = LocalContext.current
val orders = remember { mutableStateListOf<Order>() }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).orderDao().getAll(userId).collect { data ->
orders.clear()
orders.addAll(data)
}
}
}
fun OrderList(
navController: NavController?,
userId: Int?,
viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val ordersUiState by viewModel.orderListUiState.collectAsState()
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
) {
items(orders) { order ->
items(ordersUiState.orderList) { order ->
val orderId = Screen.OrderView.route.replace("{id}", order.uid.toString())
Box(
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.graphics.BitmapFactory
@ -18,42 +18,33 @@ 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.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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 com.example.myapplication.database.AppDatabase
import com.example.myapplication.entities.model.SessionFromOrder
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun OrderView(id: Int) {
val context = LocalContext.current
val sessions = remember {
mutableStateListOf<SessionFromOrder>()
}
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
sessions.clear()
sessions.addAll(AppDatabase.getInstance(context).orderDao().getByUid(id))
}
}
fun OrderView(
id: Int,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val orderUiState by viewModel.orderUiState.collectAsState()
LazyColumn(
modifier = Modifier
.padding(10.dp)
) {
items(sessions) { session ->
items(orderUiState.sessionList) { session ->
val count = remember { mutableStateOf(session.count) }
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formattedDate = dateFormatter.format(session.dateTime)

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.clickable
@ -28,7 +28,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.datastore.SettingData
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.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.entities.model.OrderSessionCrossRef
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
@Dao
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.Delete
import androidx.room.Insert
import androidx.room.Query
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
interface SessionDao {
@Query("select * from sessions where sessions.uid = :uid")
fun getByUid(uid: Int): Flow<Session>
@Insert
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.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.entities.model.SessionFromCart
import com.example.myapplication.entities.model.User
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.User
import kotlinx.coroutines.flow.Flow
@Dao
@ -15,12 +15,12 @@ interface UserDao {
fun getAll(): Flow<List<User>>
@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 " +
"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 " +
"GROUP BY orders_sessions.session_id "
"GROUP BY users_sessions.session_id "
)
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.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.entities.model.SessionFromCart
import com.example.myapplication.entities.model.UserSessionCrossRef
import kotlinx.coroutines.flow.Flow
import com.example.myapplication.database.entities.model.UserSessionCrossRef
@Dao
interface UserSessionCrossRefDao {
@ -19,4 +17,7 @@ interface UserSessionCrossRefDao {
@Delete
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.Entity
@ -8,7 +8,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "cinemas")
data class Cinema(
@PrimaryKey(autoGenerate = true)
val uid: Int?,
val uid: Int = 0,
val name: String,
val description: String,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
@ -21,18 +21,37 @@ data class Cinema(
description: String,
image: ByteArray?,
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 {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Cinema
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
}
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 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.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.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.Entity
@ -13,21 +13,21 @@ import org.threeten.bp.LocalDateTime
entity = Cinema::class,
parentColumns = ["uid"],
childColumns = ["cinema_id"],
onDelete = ForeignKey.RESTRICT,
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class Session(
@PrimaryKey(autoGenerate = true)
val uid: Int?,
val uid: Int = 0,
@ColumnInfo(name = "date_time")
val dateTime: LocalDateTime,
val price: Double,
@ColumnInfo(name = "max_count")
val maxCount: Int,
@ColumnInfo(name = "cinema_id", index = true)
val cinemaId: Int?,
val cinemaId: Int = 0,
) {
@Ignore
constructor(
@ -35,18 +35,39 @@ data class Session(
price: Double,
maxCount: Int,
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 {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Session
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
}
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.Relation
@ -6,7 +6,7 @@ import org.threeten.bp.LocalDateTime
data class SessionFromCart(
@ColumnInfo(name = "uid")
val uid: Int?,
val uid: Int = 0,
@ColumnInfo(name = "date_time")
val dateTime: LocalDateTime,
val price: Double,
@ -14,7 +14,7 @@ data class SessionFromCart(
val availableCount: Int,
val count: Int,
@ColumnInfo(name = "cinema_id")
val cinemaId: Int?,
val cinemaId: Int = 0,
@Relation(
parentColumn = "cinema_id",
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.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.PrimaryKey
@ -6,7 +6,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val uid: Int?,
val uid: Int = 0,
val login: 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:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000000"
<path
android:fillColor="#000000"
android:pathData="M19,13H5v-2h14v2z" />
</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="Cinema_main_title">Фильмы</string>
<string name="Cinema_view_title">Фильм</string>
<string name="Session_view_title">Сеанс</string>
<string name="Order_view_title">Заказ</string>
<string name="Cinema_name">Название</string>
<string name="Cinema_year">Год</string>
@ -11,4 +12,11 @@
<string name="Order_title">Мои заказы</string>
<string name="Profile_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>

View File

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