lw4
This commit is contained in:
parent
5a18ee67fc
commit
e4bc55da4e
@ -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"
|
||||
|
@ -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 |
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
SwipeToDelete(
|
||||
dismissState = dismissState,
|
||||
session = session,
|
||||
onChangeCount = onChangeCount
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Button(
|
||||
onClick = { onAddToOrder(cartUiState.sessionList, 1) },
|
||||
modifier = Modifier
|
||||
.padding(all = 10.dp)
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
) { Text("Купить") }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SwipeToDelete(
|
||||
dismissState: DismissState,
|
||||
session: SessionFromCart,
|
||||
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
|
||||
) {
|
||||
SwipeToDismiss(
|
||||
state = dismissState,
|
||||
directions = setOf(
|
||||
DismissDirection.EndToStart
|
||||
),
|
||||
background = {
|
||||
val backgroundColor by animateColorAsState(
|
||||
when (dismissState.targetValue) {
|
||||
DismissValue.DismissedToStart -> Color.Red.copy(alpha = 0.8f)
|
||||
else -> MaterialTheme.colorScheme.background
|
||||
},
|
||||
label = ""
|
||||
)
|
||||
val iconScale by animateFloatAsState(
|
||||
targetValue = if (dismissState.targetValue == DismissValue.DismissedToStart) 1.3f else 0.5f,
|
||||
label = ""
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = backgroundColor)
|
||||
.padding(end = 16.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
items(sessions) { session ->
|
||||
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,
|
||||
) {
|
||||
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,
|
||||
@ -162,20 +290,8 @@ 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)
|
||||
@Composable
|
||||
@ -184,7 +300,7 @@ fun CartPreview() {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Cart(1)
|
||||
Cart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
@ -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())
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
@ -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
|
@ -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())
|
@ -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)
|
@ -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())
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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 {
|
@ -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)
|
||||
|
@ -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>>
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
) {
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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,
|
||||
)
|
@ -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,
|
||||
)
|
@ -3,6 +3,7 @@
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="M19,13H5v-2h14v2z"/>
|
||||
<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 |
@ -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>
|
@ -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>
|
@ -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).
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user