Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
167d5ccc7d | |||
7f0c3a638d | |||
f499c56d14 | |||
e4bc55da4e | |||
5a18ee67fc | |||
f879990c39 |
41
.idea/inspectionProfiles/Project_Default.xml
Normal file
41
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.8.10" />
|
<option name="version" value="1.8.20" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
id("org.jetbrains.kotlin.plugin.serialization")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -9,7 +11,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.example.myapplication"
|
applicationId = "com.example.myapplication"
|
||||||
minSdk = 24
|
minSdk = 26
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
@ -23,21 +25,25 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.4.3"
|
kotlinCompilerExtensionVersion = "1.4.5"
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
@ -47,15 +53,42 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
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")
|
||||||
|
|
||||||
|
// Core
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
implementation("androidx.activity:activity-compose:1.7.0")
|
|
||||||
|
// UI
|
||||||
|
implementation("androidx.activity:activity-compose:1.7.2")
|
||||||
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||||
|
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3:1.1.2")
|
||||||
|
implementation("androidx.compose.material:material:1.4.3")
|
||||||
|
|
||||||
|
// Room
|
||||||
|
val room_version = "2.5.2"
|
||||||
|
implementation("androidx.room:room-runtime:$room_version")
|
||||||
|
annotationProcessor("androidx.room:room-compiler:$room_version")
|
||||||
|
ksp("androidx.room:room-compiler:$room_version")
|
||||||
|
implementation("androidx.room:room-ktx:$room_version")
|
||||||
|
implementation("androidx.room:room-paging:$room_version")
|
||||||
|
|
||||||
|
// retrofit
|
||||||
|
val retrofitVersion = "2.9.0"
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
|
||||||
|
implementation("androidx.paging:paging-compose:3.2.1")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
|
||||||
|
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
|
||||||
|
|
||||||
|
// Tests
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package com.example.myapplication
|
package com.example.myapplication
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import org.junit.Assert.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
*
|
*
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".CinemaApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@ -10,13 +13,14 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.MyApplication"
|
android:theme="@style/Theme.Pmudemo"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainComposeActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.MyApplication">
|
android:theme="@style/Theme.Pmudemo">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +0,0 @@
|
|||||||
package com.example.myapplication
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.example.myapplication.ui.theme.MyApplicationTheme
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContent {
|
|
||||||
MyApplicationTheme {
|
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
|
||||||
Greeting("Android")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
|
||||||
Text(
|
|
||||||
text = "Hello $name!",
|
|
||||||
modifier = modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun GreetingPreview() {
|
|
||||||
MyApplicationTheme {
|
|
||||||
Greeting("Android")
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
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
|
||||||
|
|
||||||
|
class MainComposeActivity : ComponentActivity() {
|
||||||
|
private val dataStoreManager = DataStoreManager(this)
|
||||||
|
private val isDarkTheme = mutableStateOf(true)
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
application.deleteDatabase("pmy-db")
|
||||||
|
setContent {
|
||||||
|
PmudemoTheme(darkTheme = isDarkTheme.value) {
|
||||||
|
LaunchedEffect(key1 = true) {
|
||||||
|
dataStoreManager.getSettings().collect { setting ->
|
||||||
|
isDarkTheme.value = setting.isDarkTheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
MainNavbar(
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
dataStoreManager = dataStoreManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import com.example.myapplication.database.entities.model.LocalDateTimeConverter
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.descriptors.*
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import org.threeten.bp.DateTimeUtils.toLocalDateTime
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Serializer(forClass = LocalDateTime::class)
|
||||||
|
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||||
|
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
encoder.encodeString(dateFormatter.format(value).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): LocalDateTime {
|
||||||
|
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
return LocalDateTime.parse(decoder.decodeString(), dateFormatter)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.api.order.OrderRemote
|
||||||
|
import com.example.myapplication.api.session.SessionFromCinemaRemote
|
||||||
|
import com.example.myapplication.api.session.SessionRemote
|
||||||
|
import com.example.myapplication.api.session.SessionWithCinemaRemote
|
||||||
|
import com.example.myapplication.api.user.UserRemote
|
||||||
|
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.contextual
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.DELETE
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.PUT
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface MyServerService {
|
||||||
|
@GET("orders")
|
||||||
|
suspend fun getOrders(): List<OrderRemote>
|
||||||
|
|
||||||
|
@GET("users")
|
||||||
|
suspend fun getUsers(): List<UserRemote>
|
||||||
|
|
||||||
|
@GET("cinemas")
|
||||||
|
suspend fun getCinemas(
|
||||||
|
@Query("_page") page: Int,
|
||||||
|
@Query("_limit") limit: Int,
|
||||||
|
): List<CinemaRemote>
|
||||||
|
|
||||||
|
@GET("cinemas/{id}")
|
||||||
|
suspend fun getCinema(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): CinemaRemote
|
||||||
|
|
||||||
|
@POST("cinemas")
|
||||||
|
suspend fun createCinema(
|
||||||
|
@Body cinema: CinemaRemote,
|
||||||
|
): CinemaRemote
|
||||||
|
|
||||||
|
@PUT("cinemas/{id}")
|
||||||
|
suspend fun updateCinema(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body cinema: CinemaRemote,
|
||||||
|
): CinemaRemote
|
||||||
|
|
||||||
|
@DELETE("cinemas/{id}")
|
||||||
|
suspend fun deleteCinema(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@GET("cinemas/{cinemaId}/sessions")
|
||||||
|
suspend fun getSessionsForCinema(
|
||||||
|
@Path("cinemaId") cinemaId: Int
|
||||||
|
): List<SessionFromCinemaRemote>
|
||||||
|
|
||||||
|
@GET("sessions/{id}?_expand=cinema")
|
||||||
|
suspend fun getSession(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): SessionWithCinemaRemote
|
||||||
|
|
||||||
|
@POST("sessions")
|
||||||
|
suspend fun createSession(
|
||||||
|
@Body session: SessionRemote,
|
||||||
|
): SessionRemote
|
||||||
|
|
||||||
|
@PUT("sessions/{id}")
|
||||||
|
suspend fun updateSession(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body session: SessionRemote,
|
||||||
|
): SessionRemote
|
||||||
|
|
||||||
|
@DELETE("sessions/{id}")
|
||||||
|
suspend fun deleteSession(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): SessionFromCinemaRemote
|
||||||
|
|
||||||
|
@GET("users/{id}")
|
||||||
|
suspend fun getUserCart(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): UserRemote
|
||||||
|
|
||||||
|
@PUT("users/{id}")
|
||||||
|
suspend fun updateUserCart(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body userRemote: UserRemote,
|
||||||
|
): UserRemote
|
||||||
|
|
||||||
|
@GET("orders")
|
||||||
|
suspend fun getOrders(
|
||||||
|
@Query("_page") page: Int,
|
||||||
|
@Query("_limit") limit: Int,
|
||||||
|
): List<OrderRemote>
|
||||||
|
|
||||||
|
@GET("orders/{id}")
|
||||||
|
suspend fun getOrder(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): OrderRemote
|
||||||
|
|
||||||
|
@POST("orders")
|
||||||
|
suspend fun createOrder(
|
||||||
|
@Body cinema: OrderRemote,
|
||||||
|
): OrderRemote
|
||||||
|
|
||||||
|
@PUT("orders/{id}")
|
||||||
|
suspend fun updateOrder(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body orderRemote: OrderRemote,
|
||||||
|
): OrderRemote
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
//private const val BASE_URL = "http://192.168.154.166:8079/"
|
||||||
|
private const val BASE_URL = "http://192.168.0.101:8079/"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: MyServerService? = null
|
||||||
|
|
||||||
|
fun getInstance(): MyServerService {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val logger = HttpLoggingInterceptor()
|
||||||
|
logger.level = HttpLoggingInterceptor.Level.BASIC
|
||||||
|
val client = OkHttpClient.Builder().addInterceptor(logger).build()
|
||||||
|
val json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
serializersModule = SerializersModule {
|
||||||
|
contextual(LocalDateTimeSerializer)
|
||||||
|
}
|
||||||
|
} // Создаем экземпляр Json с ignoreUnknownKeys = true
|
||||||
|
return Retrofit.Builder().baseUrl(BASE_URL).client(client)
|
||||||
|
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // Применяем конфигурацию Json
|
||||||
|
.build().create(MyServerService::class.java).also { INSTANCE = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.example.myapplication.api.cinema
|
||||||
|
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CinemaRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val name: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val image: ByteArray? = null,
|
||||||
|
val year: Long = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CinemaRemote.toCinema(): Cinema = Cinema(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
year
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Cinema.toCinemaRemote(): CinemaRemote = CinemaRemote(
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
year
|
||||||
|
)
|
@ -0,0 +1,121 @@
|
|||||||
|
package com.example.myapplication.api.cinema
|
||||||
|
|
||||||
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.LoadType
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.paging.RemoteMediator
|
||||||
|
import androidx.room.withTransaction
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.session.toSession
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineCinemaRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeys
|
||||||
|
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
class CinemaRemoteMediator(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbCinemaRepository: OfflineCinemaRepository,
|
||||||
|
private val dbSessionRepository: OfflineSessionRepository,
|
||||||
|
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||||
|
private val database: AppDatabase
|
||||||
|
) : RemoteMediator<Int, Cinema>() {
|
||||||
|
|
||||||
|
override suspend fun initialize(): InitializeAction {
|
||||||
|
return InitializeAction.LAUNCH_INITIAL_REFRESH
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(
|
||||||
|
loadType: LoadType,
|
||||||
|
state: PagingState<Int, Cinema>
|
||||||
|
): MediatorResult {
|
||||||
|
val page = when (loadType) {
|
||||||
|
LoadType.REFRESH -> {
|
||||||
|
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
|
||||||
|
remoteKeys?.nextKey?.minus(1) ?: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadType.PREPEND -> {
|
||||||
|
val remoteKeys = getRemoteKeyForFirstItem(state)
|
||||||
|
remoteKeys?.prevKey
|
||||||
|
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadType.APPEND -> {
|
||||||
|
val remoteKeys = getRemoteKeyForLastItem(state)
|
||||||
|
remoteKeys?.nextKey
|
||||||
|
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val cinemas = service.getCinemas(page, state.config.pageSize).map { it.toCinema() }
|
||||||
|
val cinemasWithSessions = cinemas.map { cinema ->
|
||||||
|
service.getSessionsForCinema(cinema.uid).map {
|
||||||
|
service.getSession(it.id).toSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val endOfPaginationReached = cinemas.isEmpty()
|
||||||
|
database.withTransaction {
|
||||||
|
if (loadType == LoadType.REFRESH) {
|
||||||
|
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CINEMA)
|
||||||
|
dbSessionRepository.clearSessions()
|
||||||
|
dbCinemaRepository.clearCinemas()
|
||||||
|
}
|
||||||
|
val prevKey = if (page == 1) null else page - 1
|
||||||
|
val nextKey = if (endOfPaginationReached) null else page + 1
|
||||||
|
val keys = cinemas.map {
|
||||||
|
RemoteKeys(
|
||||||
|
entityId = it.uid,
|
||||||
|
type = RemoteKeyType.CINEMA,
|
||||||
|
prevKey = prevKey,
|
||||||
|
nextKey = nextKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
|
dbCinemaRepository.insertCinemas(cinemas)
|
||||||
|
cinemasWithSessions.forEach {
|
||||||
|
try {
|
||||||
|
dbSessionRepository.insertSessions(it)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
return MediatorResult.Error(exception)
|
||||||
|
} catch (exception: HttpException) {
|
||||||
|
return MediatorResult.Error(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Cinema>): RemoteKeys? {
|
||||||
|
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
|
||||||
|
?.let { cinema ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(cinema.uid, RemoteKeyType.CINEMA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Cinema>): RemoteKeys? {
|
||||||
|
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
|
||||||
|
?.let { cinema ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(cinema.uid, RemoteKeyType.CINEMA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRemoteKeyClosestToCurrentPosition(
|
||||||
|
state: PagingState<Int, Cinema>
|
||||||
|
): RemoteKeys? {
|
||||||
|
return state.anchorPosition?.let { position ->
|
||||||
|
state.closestItemToPosition(position)?.uid?.let { cinemaUid ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(cinemaUid, RemoteKeyType.CINEMA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.example.myapplication.api.cinema
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.database.AppContainer
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
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 com.example.myapplication.database.entities.repository.OfflineCinemaRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
|
||||||
|
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class RestCinemaRepository(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbCinemaRepository: OfflineCinemaRepository,
|
||||||
|
private val dbSessionRepository: OfflineSessionRepository,
|
||||||
|
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||||
|
private val database: AppDatabase
|
||||||
|
) : CinemaRepository {
|
||||||
|
override fun getAllCinemas(): Flow<PagingData<Cinema>> {
|
||||||
|
Log.d(RestCinemaRepository::class.simpleName, "Get cinemas")
|
||||||
|
|
||||||
|
val pagingSourceFactory = { dbCinemaRepository.getAllCinemasPagingSource() }
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
return Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
remoteMediator = CinemaRemoteMediator(
|
||||||
|
service,
|
||||||
|
dbCinemaRepository,
|
||||||
|
dbSessionRepository,
|
||||||
|
dbRemoteKeyRepository,
|
||||||
|
database,
|
||||||
|
),
|
||||||
|
pagingSourceFactory = pagingSourceFactory
|
||||||
|
).flow
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCinema(uid: Int): CinemaWithSessions {
|
||||||
|
val cinema = service.getCinema(uid).toCinema()
|
||||||
|
|
||||||
|
val sessions = service.getSessionsForCinema(uid).map { x ->
|
||||||
|
SessionFromCinema(
|
||||||
|
x.id,
|
||||||
|
x.dateTime,
|
||||||
|
x.price,
|
||||||
|
x.maxCount - service.getOrders().flatMap { order ->
|
||||||
|
order.sessions.filter { session -> session.id == x.id }
|
||||||
|
}.sumOf { session -> session.count },
|
||||||
|
uid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return CinemaWithSessions(cinema, sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertCinema(cinema: Cinema) {
|
||||||
|
service.createCinema(cinema.toCinemaRemote()).toCinema()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateCinema(cinema: Cinema) {
|
||||||
|
service.updateCinema(cinema.uid, cinema.toCinemaRemote()).toCinema()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCinema(cinema: Cinema) {
|
||||||
|
val cart = service.getUsers()
|
||||||
|
cart.forEach { userRemote ->
|
||||||
|
userRemote.sessions = userRemote.sessions.filter { x -> x.cinemaId != cinema.uid }
|
||||||
|
service.updateUserCart(userRemote.id, userRemote)
|
||||||
|
}
|
||||||
|
val orders = service.getOrders()
|
||||||
|
orders.forEach { orderRemote ->
|
||||||
|
orderRemote.sessions = orderRemote.sessions.filter { x -> x.cinemaId != cinema.uid }
|
||||||
|
service.updateOrder(orderRemote.id, orderRemote)
|
||||||
|
}
|
||||||
|
service.deleteCinema(cinema.uid)
|
||||||
|
dbCinemaRepository.deleteCinema(cinema)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.myapplication.api.order
|
||||||
|
|
||||||
|
import com.example.myapplication.api.session.SessionFromOrderRemote
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OrderRemote(
|
||||||
|
val id: Int = 0, val userId: Int = 0, var sessions: List<SessionFromOrderRemote> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun OrderRemote.toOrder(): Order = Order(
|
||||||
|
id, userId
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
|
||||||
|
uid, userId!!, sessions = emptyList()
|
||||||
|
)
|
@ -0,0 +1,106 @@
|
|||||||
|
package com.example.myapplication.api.order
|
||||||
|
|
||||||
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.LoadType
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.paging.RemoteMediator
|
||||||
|
import androidx.room.withTransaction
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineOrderRepository
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeys
|
||||||
|
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
class OrderRemoteMediator(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbOrderRepository: OfflineOrderRepository,
|
||||||
|
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||||
|
private val database: AppDatabase
|
||||||
|
) : RemoteMediator<Int, Order>() {
|
||||||
|
|
||||||
|
override suspend fun initialize(): InitializeAction {
|
||||||
|
return InitializeAction.LAUNCH_INITIAL_REFRESH
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(
|
||||||
|
loadType: LoadType,
|
||||||
|
state: PagingState<Int, Order>
|
||||||
|
): MediatorResult {
|
||||||
|
val page = when (loadType) {
|
||||||
|
LoadType.REFRESH -> {
|
||||||
|
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
|
||||||
|
remoteKeys?.nextKey?.minus(1) ?: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadType.PREPEND -> {
|
||||||
|
val remoteKeys = getRemoteKeyForFirstItem(state)
|
||||||
|
remoteKeys?.prevKey
|
||||||
|
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadType.APPEND -> {
|
||||||
|
val remoteKeys = getRemoteKeyForLastItem(state)
|
||||||
|
remoteKeys?.nextKey
|
||||||
|
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val orders = service.getOrders(page, state.config.pageSize).map { it.toOrder() }
|
||||||
|
val endOfPaginationReached = orders.isEmpty()
|
||||||
|
database.withTransaction {
|
||||||
|
if (loadType == LoadType.REFRESH) {
|
||||||
|
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
|
||||||
|
dbOrderRepository.clearOrders()
|
||||||
|
}
|
||||||
|
val prevKey = if (page == 1) null else page - 1
|
||||||
|
val nextKey = if (endOfPaginationReached) null else page + 1
|
||||||
|
val keys = orders.map {
|
||||||
|
RemoteKeys(
|
||||||
|
entityId = it.uid,
|
||||||
|
type = RemoteKeyType.ORDER,
|
||||||
|
prevKey = prevKey,
|
||||||
|
nextKey = nextKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
|
dbOrderRepository.insertOrders(orders)
|
||||||
|
}
|
||||||
|
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
return MediatorResult.Error(exception)
|
||||||
|
} catch (exception: HttpException) {
|
||||||
|
return MediatorResult.Error(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Order>): RemoteKeys? {
|
||||||
|
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
|
||||||
|
?.let { order ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Order>): RemoteKeys? {
|
||||||
|
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
|
||||||
|
?.let { order ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRemoteKeyClosestToCurrentPosition(
|
||||||
|
state: PagingState<Int, Order>
|
||||||
|
): RemoteKeys? {
|
||||||
|
return state.anchorPosition?.let { position ->
|
||||||
|
state.closestItemToPosition(position)?.uid?.let { orderUid ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(orderUid, RemoteKeyType.ORDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package com.example.myapplication.api.order
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.cinema.toCinemaRemote
|
||||||
|
import com.example.myapplication.api.session.toSessionFromOrder
|
||||||
|
import com.example.myapplication.database.AppContainer
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
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.OrderRepository
|
||||||
|
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class RestOrderRepository(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbOrderRepository: OfflineOrderRepository,
|
||||||
|
private val dbCinemaRepository: OfflineCinemaRepository,
|
||||||
|
private val dbOrderSessionRepository: OfflineOrderSessionRepository,
|
||||||
|
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||||
|
private val database: AppDatabase
|
||||||
|
) : OrderRepository {
|
||||||
|
override fun getAllOrders(userId: Int?): Flow<PagingData<Order>> {
|
||||||
|
Log.d(RestOrderRepository::class.simpleName, "Get orders")
|
||||||
|
|
||||||
|
val pagingSourceFactory = { dbOrderRepository.getAllOrdersPagingSource(userId) }
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
return Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
remoteMediator = OrderRemoteMediator(
|
||||||
|
service,
|
||||||
|
dbOrderRepository,
|
||||||
|
dbRemoteKeyRepository,
|
||||||
|
database,
|
||||||
|
),
|
||||||
|
pagingSourceFactory = pagingSourceFactory
|
||||||
|
).flow
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getOrder(uid: Int): List<SessionFromOrder> {
|
||||||
|
val order = service.getOrder(uid)
|
||||||
|
|
||||||
|
dbOrderSessionRepository.deleteOrderSessions(uid)
|
||||||
|
order.sessions.map {
|
||||||
|
dbOrderSessionRepository.insertOrderSession(
|
||||||
|
OrderSessionCrossRef(
|
||||||
|
uid,
|
||||||
|
it.id,
|
||||||
|
it.frozenPrice,
|
||||||
|
it.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return order.sessions.map { x -> x.toSessionFromOrder(dbCinemaRepository.getCinema(x.cinemaId).cinema.toCinemaRemote()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertOrder(order: Order): Long {
|
||||||
|
return dbOrderRepository.insertOrder(service.createOrder(order.toOrderRemote()).toOrder())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.example.myapplication.api.ordersession
|
||||||
|
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.session.SessionFromOrderRemote
|
||||||
|
import com.example.myapplication.api.session.toSession
|
||||||
|
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OrderSessionRepository
|
||||||
|
|
||||||
|
class RestOrderSessionRepository(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbOrderSessionRepository: OfflineOrderSessionRepository
|
||||||
|
) : OrderSessionRepository {
|
||||||
|
override suspend fun insertOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
|
||||||
|
var orderRemote = service.getOrder(orderSessionCrossRef.orderId)
|
||||||
|
val session = service.getSession(orderSessionCrossRef.sessionId).toSession()
|
||||||
|
|
||||||
|
val sessionFromOrder = SessionFromOrderRemote(
|
||||||
|
session.uid,
|
||||||
|
session.dateTime,
|
||||||
|
session.price,
|
||||||
|
orderSessionCrossRef.count,
|
||||||
|
session.cinemaId
|
||||||
|
)
|
||||||
|
|
||||||
|
val updatedSessions = orderRemote.sessions.toMutableList()
|
||||||
|
updatedSessions.add(sessionFromOrder)
|
||||||
|
|
||||||
|
orderRemote = orderRemote.copy(sessions = updatedSessions)
|
||||||
|
service.updateOrder(orderSessionCrossRef.orderId, orderRemote)
|
||||||
|
dbOrderSessionRepository.insertOrderSession(orderSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.api.cinema.toCinema
|
||||||
|
import com.example.myapplication.api.session.SessionFromCinemaRemote
|
||||||
|
import com.example.myapplication.api.session.toSessionFromCinema
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
import com.example.myapplication.database.entities.model.CinemaWithSessions
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/*
|
||||||
|
@Serializable
|
||||||
|
data class CinemaWithSessionsRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val name: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val image: ByteArray? = null,
|
||||||
|
val year: Long = 0,
|
||||||
|
@SerialName("sessions")
|
||||||
|
val sessions: List<SessionFromCinemaRemote>,
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as CinemaWithSessionsRemote
|
||||||
|
|
||||||
|
if (id != other.id) return false
|
||||||
|
if (name != other.name) return false
|
||||||
|
if (description != other.description) return false
|
||||||
|
if (image != null) {
|
||||||
|
if (other.image == null) return false
|
||||||
|
if (!image.contentEquals(other.image)) return false
|
||||||
|
} else if (other.image != null) return false
|
||||||
|
if (year != other.year) return false
|
||||||
|
if (sessions != other.sessions) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = id
|
||||||
|
result = 31 * result + name.hashCode()
|
||||||
|
result = 31 * result + description.hashCode()
|
||||||
|
result = 31 * result + (image?.contentHashCode() ?: 0)
|
||||||
|
result = 31 * result + year.hashCode()
|
||||||
|
result = 31 * result + sessions.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CinemaWithSessionsRemote.toCinemaWithSessions(): CinemaWithSessions = CinemaWithSessions(
|
||||||
|
Cinema(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
year
|
||||||
|
),
|
||||||
|
sessions.map { x -> x.toSessionFromCinema() }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Cinema.toCinemaWithSessionsRemote(): CinemaWithSessionsRemote = CinemaWithSessionsRemote(
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
year,
|
||||||
|
sessions = emptyList()
|
||||||
|
)*/
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.SessionRepository
|
||||||
|
|
||||||
|
class RestSessionRepository(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbSessionRepository: OfflineSessionRepository,
|
||||||
|
private val dbUserSessionRepository: OfflineUserSessionRepository,
|
||||||
|
private val dbOrderSessionRepository: OfflineOrderSessionRepository,
|
||||||
|
) : SessionRepository {
|
||||||
|
override suspend fun getSession(uid: Int): Session {
|
||||||
|
return service.getSession(uid).toSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertSession(session: Session) {
|
||||||
|
dbSessionRepository.insertSession(
|
||||||
|
service.createSession(session.toSessionRemote()).toSession()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateSession(session: Session) {
|
||||||
|
dbSessionRepository.updateSession(
|
||||||
|
service.updateSession(
|
||||||
|
session.uid,
|
||||||
|
session.toSessionRemote()
|
||||||
|
).toSession()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteSession(session: Session) {
|
||||||
|
val cart = service.getUsers()
|
||||||
|
cart.forEach { userRemote ->
|
||||||
|
userRemote.sessions = userRemote.sessions.filter { x -> x.id != session.uid }
|
||||||
|
service.updateUserCart(userRemote.id, userRemote)
|
||||||
|
}
|
||||||
|
val orders = service.getOrders()
|
||||||
|
orders.forEach { orderRemote ->
|
||||||
|
orderRemote.sessions = orderRemote.sessions.filter { x -> x.id != session.uid }
|
||||||
|
service.updateOrder(orderRemote.id, orderRemote)
|
||||||
|
}
|
||||||
|
service.deleteSession(session.uid)
|
||||||
|
dbUserSessionRepository.deleteSessionsByUid(session.uid)
|
||||||
|
dbOrderSessionRepository.deleteSessionsByUid(session.uid)
|
||||||
|
dbSessionRepository.deleteSession(session)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.api.cinema.toCinema
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromCart
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SessionFromCartRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
var count: Int = 0,
|
||||||
|
var cinemaId: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionFromCartRemote.toSessionFromCart(cinema: CinemaRemote, dateTime: LocalDateTime, price: Double, availableCount: Int): SessionFromCart =
|
||||||
|
SessionFromCart(
|
||||||
|
id, dateTime, price, availableCount, count, cinema.id, cinema.toCinema()
|
||||||
|
)
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromCinema
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SessionFromCinemaRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
@Contextual
|
||||||
|
val dateTime: LocalDateTime = LocalDateTime.MIN,
|
||||||
|
val price: Double = 0.0,
|
||||||
|
val maxCount: Int = 0,
|
||||||
|
val availableCount: Int = 0,
|
||||||
|
val cinemaId: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionFromCinemaRemote.toSessionFromCinema(): SessionFromCinema = SessionFromCinema(
|
||||||
|
id,
|
||||||
|
dateTime,
|
||||||
|
price,
|
||||||
|
availableCount,
|
||||||
|
cinemaId
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionFromCinema.toSessionFromCinemaRemote(): SessionFromCinemaRemote = SessionFromCinemaRemote(
|
||||||
|
uid,
|
||||||
|
dateTime,
|
||||||
|
price,
|
||||||
|
availableCount,
|
||||||
|
cinemaId
|
||||||
|
)
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.api.cinema.toCinema
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SessionFromOrderRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
@Contextual val dateTime: LocalDateTime = LocalDateTime.MIN,
|
||||||
|
val frozenPrice: Double = 0.0,
|
||||||
|
val count: Int = 0,
|
||||||
|
val cinemaId: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionFromOrderRemote.toSessionFromOrder(cinema: CinemaRemote): SessionFromOrder =
|
||||||
|
SessionFromOrder(
|
||||||
|
id, dateTime, frozenPrice, count, cinemaId, cinema.toCinema()
|
||||||
|
)
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.api.cinema.toCinema
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SessionRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
@Contextual
|
||||||
|
val dateTime: LocalDateTime,
|
||||||
|
val price: Double,
|
||||||
|
val maxCount: Int,
|
||||||
|
val cinemaId: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionRemote.toSession(): Session = Session(
|
||||||
|
id,
|
||||||
|
dateTime,
|
||||||
|
price,
|
||||||
|
maxCount,
|
||||||
|
cinemaId
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Session.toSessionRemote(): SessionRemote = SessionRemote(
|
||||||
|
uid,
|
||||||
|
dateTime,
|
||||||
|
price,
|
||||||
|
maxCount,
|
||||||
|
cinemaId
|
||||||
|
)
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SessionWithCinemaRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
@Contextual
|
||||||
|
val dateTime: LocalDateTime,
|
||||||
|
val price: Double,
|
||||||
|
val maxCount: Int,
|
||||||
|
val cinemaId: Int = 0,
|
||||||
|
val cinema: CinemaRemote,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionWithCinemaRemote.toSession(): Session = Session(
|
||||||
|
id,
|
||||||
|
dateTime,
|
||||||
|
price,
|
||||||
|
maxCount,
|
||||||
|
cinemaId
|
||||||
|
)
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.example.myapplication.api.user
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.session.toSessionFromCart
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromCart
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
import com.example.myapplication.database.entities.model.UserSessionCrossRef
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineUserRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.UserRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class RestUserRepository(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbUserRepository: OfflineUserRepository,
|
||||||
|
private val dbUserSessionRepository: OfflineUserSessionRepository,
|
||||||
|
) : UserRepository {
|
||||||
|
override fun getAllUsers(): Flow<List<User>> {
|
||||||
|
Log.d(RestUserRepository::class.simpleName, "Get users")
|
||||||
|
return dbUserRepository.getAllUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> {
|
||||||
|
val cart = service.getUserCart(userId)
|
||||||
|
dbUserSessionRepository.deleteUserSessions(userId)
|
||||||
|
cart.sessions.map { sessionFromCartRemote ->
|
||||||
|
dbUserSessionRepository.insertUserSession(
|
||||||
|
UserSessionCrossRef(
|
||||||
|
userId,
|
||||||
|
sessionFromCartRemote.id,
|
||||||
|
sessionFromCartRemote.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cart.sessions.map {
|
||||||
|
val session = service.getSession(it.id)
|
||||||
|
it.toSessionFromCart(
|
||||||
|
session.cinema,
|
||||||
|
session.dateTime,
|
||||||
|
session.price,
|
||||||
|
session.maxCount - service.getOrders().flatMap { order ->
|
||||||
|
order.sessions.filter { session -> session.id == it.id }
|
||||||
|
}.sumOf { session -> session.count })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUser(user: User) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateUser(user: User) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUser(user: User) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.example.myapplication.api.user
|
||||||
|
|
||||||
|
import com.example.myapplication.api.session.SessionFromCartRemote
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val login: String = "",
|
||||||
|
val password: String = "",
|
||||||
|
var sessions: List<SessionFromCartRemote> = emptyList()
|
||||||
|
)
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.example.myapplication.api.usersession
|
||||||
|
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.session.SessionFromCartRemote
|
||||||
|
import com.example.myapplication.api.session.toSession
|
||||||
|
import com.example.myapplication.database.entities.model.UserSessionCrossRef
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.UserSessionRepository
|
||||||
|
|
||||||
|
class RestUserSessionRepository(
|
||||||
|
private val service: MyServerService,
|
||||||
|
private val dbUserSessionRepository: OfflineUserSessionRepository
|
||||||
|
) : UserSessionRepository {
|
||||||
|
override suspend fun insertUserSession(userSessionCrossRef: UserSessionCrossRef) {
|
||||||
|
var cartSessions = service.getUserCart(userSessionCrossRef.userId)
|
||||||
|
cartSessions.sessions.forEach { session ->
|
||||||
|
if (session.id == userSessionCrossRef.sessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val session = service.getSession(userSessionCrossRef.sessionId).toSession()
|
||||||
|
|
||||||
|
val sessionFromCart = SessionFromCartRemote(
|
||||||
|
session.uid,
|
||||||
|
userSessionCrossRef.count,
|
||||||
|
session.cinemaId,
|
||||||
|
)
|
||||||
|
|
||||||
|
val updatedSessions = cartSessions.sessions.toMutableList()
|
||||||
|
updatedSessions.add(sessionFromCart)
|
||||||
|
|
||||||
|
cartSessions = cartSessions.copy(sessions = updatedSessions)
|
||||||
|
service.updateUserCart(userSessionCrossRef.userId, cartSessions)
|
||||||
|
dbUserSessionRepository.insertUserSession(userSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef) {
|
||||||
|
val userRemote = service.getUserCart(userSessionCrossRef.userId)
|
||||||
|
if (userSessionCrossRef.count <= 0) {
|
||||||
|
userRemote.sessions =
|
||||||
|
userRemote.sessions.filter { x -> x.id != userSessionCrossRef.sessionId }
|
||||||
|
} else
|
||||||
|
userRemote.sessions.forEach {
|
||||||
|
if (it.id == userSessionCrossRef.sessionId) {
|
||||||
|
it.count = userSessionCrossRef.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service.updateUserCart(userSessionCrossRef.userId, userRemote)
|
||||||
|
dbUserSessionRepository.updateUserSession(userSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef) {
|
||||||
|
updateUserSession(userSessionCrossRef)
|
||||||
|
dbUserSessionRepository.deleteUserSession(userSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUserSessions(userId: Int) {
|
||||||
|
val userRemote = service.getUserCart(userId)
|
||||||
|
userRemote.sessions = emptyList()
|
||||||
|
service.updateUserCart(userId, userRemote)
|
||||||
|
dbUserSessionRepository.deleteUserSessions(userId)
|
||||||
|
}
|
||||||
|
}
|
317
app/src/main/java/com/example/myapplication/composeui/Cart.kt
Normal file
317
app/src/main/java/com/example/myapplication/composeui/Cart.kt
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
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
|
||||||
|
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.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.getValue
|
||||||
|
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.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.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.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Cart(
|
||||||
|
viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val cartUiState = viewModel.cartUiState
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.refreshState()
|
||||||
|
}
|
||||||
|
|
||||||
|
Cart(
|
||||||
|
cartUiState = cartUiState,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 10.dp),
|
||||||
|
onSwipe = { session: SessionFromCart, user: Int ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.removeFromCart(
|
||||||
|
session = Session(
|
||||||
|
uid = session.uid,
|
||||||
|
dateTime = session.dateTime,
|
||||||
|
price = session.price,
|
||||||
|
maxCount = 0,
|
||||||
|
cinemaId = session.cinemaId
|
||||||
|
), user = user
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChangeCount = { session: SessionFromCart, user: Int, count: Int ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.updateFromCart(
|
||||||
|
session = Session(
|
||||||
|
uid = session.uid,
|
||||||
|
dateTime = session.dateTime,
|
||||||
|
price = session.price,
|
||||||
|
maxCount = 0,
|
||||||
|
cinemaId = session.cinemaId
|
||||||
|
), userId = user, count = count, availableCount = session.availableCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAddToOrder = { sessions: List<SessionFromCart>, user: Int ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.addToOrder(sessions = sessions, userId = user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun Cart(
|
||||||
|
cartUiState: CartUiState,
|
||||||
|
modifier: Modifier,
|
||||||
|
onSwipe: (SessionFromCart, Int) -> Unit,
|
||||||
|
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
|
||||||
|
onAddToOrder: (List<SessionFromCart>, Int) -> Unit
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
items(cartUiState.sessionList, key = { it.uid.toString() }) { session ->
|
||||||
|
val dismissState: DismissState = rememberDismissState(
|
||||||
|
positionalThreshold = { 200.dp.toPx() }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
|
||||||
|
onSwipe(session, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeToDelete(
|
||||||
|
dismissState = dismissState,
|
||||||
|
session = session,
|
||||||
|
onChangeCount = onChangeCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { onAddToOrder(cartUiState.sessionList, 1) },
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) { Text("Купить") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun SwipeToDelete(
|
||||||
|
dismissState: DismissState,
|
||||||
|
session: SessionFromCart,
|
||||||
|
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
|
||||||
|
) {
|
||||||
|
SwipeToDismiss(
|
||||||
|
state = dismissState,
|
||||||
|
directions = setOf(
|
||||||
|
DismissDirection.EndToStart
|
||||||
|
),
|
||||||
|
background = {
|
||||||
|
val backgroundColor by animateColorAsState(
|
||||||
|
when (dismissState.targetValue) {
|
||||||
|
DismissValue.DismissedToStart -> Color.Red.copy(alpha = 0.8f)
|
||||||
|
else -> MaterialTheme.colorScheme.background
|
||||||
|
},
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
val iconScale by animateFloatAsState(
|
||||||
|
targetValue = if (dismissState.targetValue == DismissValue.DismissedToStart) 1.3f else 0.5f,
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = backgroundColor)
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
contentAlignment = Alignment.CenterEnd
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.scale(iconScale),
|
||||||
|
imageVector = Icons.Outlined.Delete,
|
||||||
|
contentDescription = "Delete",
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissContent = {
|
||||||
|
SessionListItem(
|
||||||
|
session = session,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.secondary),
|
||||||
|
onChangeCount = onChangeCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SessionListItem(
|
||||||
|
session: SessionFromCart,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onChangeCount: (SessionFromCart, Int, Int) -> Unit,
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
if (session.cinema.image != null)
|
||||||
|
Image(
|
||||||
|
bitmap = BitmapFactory.decodeByteArray(
|
||||||
|
session.cinema.image,
|
||||||
|
0,
|
||||||
|
session.cinema.image.size
|
||||||
|
).asImageBitmap(),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(90.dp)
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${session.cinema.name}, ${session.cinema.year}\n" +
|
||||||
|
"Цена: ${session.price}\n" +
|
||||||
|
"${currentCount}/${session.availableCount}",
|
||||||
|
color = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
shape = RoundedCornerShape(10.dp)
|
||||||
|
) // Задаем фон для кнопок
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onChangeCount(session, 1, --currentCount) }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = R.drawable.minus),
|
||||||
|
contentDescription = "Уменьшить",
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.size(10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "$currentCount",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
onChangeCount(
|
||||||
|
session,
|
||||||
|
1,
|
||||||
|
if (currentCount != session.availableCount) ++currentCount else currentCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = "Увеличить",
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.size(10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 CartPreview() {
|
||||||
|
PmudemoTheme {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
Cart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,233 @@
|
|||||||
|
package com.example.myapplication.composeui.navigation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
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.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
|
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||||
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Topbar(
|
||||||
|
navController: NavHostController,
|
||||||
|
currentScreen: Screen?
|
||||||
|
) {
|
||||||
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(color = MaterialTheme.colorScheme.primary)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
navController.previousBackStackEntry != null
|
||||||
|
&& (currentScreen == null || !currentScreen.showInBottomBar)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.clickable { navController.navigateUp() },
|
||||||
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
} else
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.clickable { navController.navigate(Screen.UserProfile.route) },
|
||||||
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = searchQuery,
|
||||||
|
onValueChange = { newValue -> searchQuery = newValue },
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(36.dp)
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
RoundedCornerShape(18.dp)
|
||||||
|
)
|
||||||
|
.padding(start = 13.dp, top = 8.dp),
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
imeAction = androidx.compose.ui.text.input.ImeAction.Search
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = { }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.clickable { },
|
||||||
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Navbar(
|
||||||
|
navController: NavHostController,
|
||||||
|
currentDestination: NavDestination?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
NavigationBar(modifier = modifier, containerColor = MaterialTheme.colorScheme.primary) {
|
||||||
|
Screen.bottomBarItems.forEach { screen ->
|
||||||
|
NavigationBarItem(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
screen.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { Text(stringResource(screen.resourceId)) },
|
||||||
|
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(screen.route) {
|
||||||
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Navhost(
|
||||||
|
navController: NavHostController,
|
||||||
|
innerPadding: PaddingValues,
|
||||||
|
isDarkTheme: MutableState<Boolean>,
|
||||||
|
dataStore: DataStoreManager,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
NavHost(
|
||||||
|
navController,
|
||||||
|
startDestination = Screen.CinemaList.route,
|
||||||
|
modifier.padding(innerPadding)
|
||||||
|
) {
|
||||||
|
composable(Screen.CinemaList.route) { CinemaList(navController) }
|
||||||
|
composable(Screen.OrderList.route) { OrderList(navController, 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(navController) }
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
Screen.OrderView.route,
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
|
) { backStackEntry ->
|
||||||
|
backStackEntry.arguments?.let { OrderView(it.getInt("id")) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun MainNavbar(
|
||||||
|
isDarkTheme: MutableState<Boolean>,
|
||||||
|
dataStoreManager: DataStoreManager
|
||||||
|
) {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentDestination = navBackStackEntry?.destination
|
||||||
|
val currentScreen = currentDestination?.route?.let { Screen.getItem(it) }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
Topbar(navController, currentScreen)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
if (currentScreen == null || currentScreen.showInBottomBar) {
|
||||||
|
Navbar(navController, currentDestination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
Navhost(navController, innerPadding, isDarkTheme, dataStoreManager)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package com.example.myapplication.composeui.navigation
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material.icons.filled.List
|
||||||
|
import androidx.compose.material.icons.filled.ShoppingCart
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import com.example.myapplication.R
|
||||||
|
|
||||||
|
enum class Screen(
|
||||||
|
val route: String,
|
||||||
|
@StringRes val resourceId: Int,
|
||||||
|
val icon: ImageVector = Icons.Filled.Favorite,
|
||||||
|
val showInBottomBar: Boolean = true
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
),
|
||||||
|
Cart(
|
||||||
|
"cart", R.string.Cart_title, Icons.Filled.ShoppingCart
|
||||||
|
),
|
||||||
|
OrderList(
|
||||||
|
"Order-list", R.string.Order_title, Icons.Filled.List
|
||||||
|
),
|
||||||
|
OrderView(
|
||||||
|
"Order-view/{id}", R.string.Order_view_title, showInBottomBar = false
|
||||||
|
),
|
||||||
|
UserProfile(
|
||||||
|
"User-profile", R.string.Profile_title, showInBottomBar = false
|
||||||
|
);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val bottomBarItems = listOf(
|
||||||
|
CinemaList,
|
||||||
|
Cart,
|
||||||
|
OrderList
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getItem(route: String): Screen? {
|
||||||
|
val findRoute = route.split("/").first()
|
||||||
|
return values().find { value -> value.route.startsWith(findRoute) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package com.example.myapplication.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.cinema.RestCinemaRepository
|
||||||
|
import com.example.myapplication.api.order.RestOrderRepository
|
||||||
|
import com.example.myapplication.api.ordersession.RestOrderSessionRepository
|
||||||
|
import com.example.myapplication.api.session.RestSessionRepository
|
||||||
|
import com.example.myapplication.api.user.RestUserRepository
|
||||||
|
import com.example.myapplication.api.usersession.RestUserSessionRepository
|
||||||
|
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.remotekeys.repository.OfflineRemoteKeyRepository
|
||||||
|
|
||||||
|
interface AppContainer {
|
||||||
|
val cinemaRestRepository: RestCinemaRepository
|
||||||
|
val sessionRestRepository: RestSessionRepository
|
||||||
|
val userRestRepository: RestUserRepository
|
||||||
|
val orderRestRepository: RestOrderRepository
|
||||||
|
val orderSessionRestRepository: RestOrderSessionRepository
|
||||||
|
val userSessionRestRepository: RestUserSessionRepository
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TIMEOUT = 5000L
|
||||||
|
const val LIMIT = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppDataContainer(private val context: Context) : AppContainer {
|
||||||
|
private val cinemaRepository: OfflineCinemaRepository by lazy {
|
||||||
|
OfflineCinemaRepository(AppDatabase.getInstance(context).cinemaDao())
|
||||||
|
}
|
||||||
|
private val orderRepository: OfflineOrderRepository by lazy {
|
||||||
|
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
|
||||||
|
}
|
||||||
|
private val orderSessionRepository: OfflineOrderSessionRepository by lazy {
|
||||||
|
OfflineOrderSessionRepository(AppDatabase.getInstance(context).orderSessionCrossRefDao())
|
||||||
|
}
|
||||||
|
private val sessionRepository: OfflineSessionRepository by lazy {
|
||||||
|
OfflineSessionRepository(AppDatabase.getInstance(context).sessionDao())
|
||||||
|
}
|
||||||
|
private val userRepository: OfflineUserRepository by lazy {
|
||||||
|
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
|
||||||
|
}
|
||||||
|
private val userSessionRepository: OfflineUserSessionRepository by lazy {
|
||||||
|
OfflineUserSessionRepository(AppDatabase.getInstance(context).userSessionCrossRefDao())
|
||||||
|
}
|
||||||
|
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
|
||||||
|
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
|
||||||
|
}
|
||||||
|
override val cinemaRestRepository: RestCinemaRepository by lazy {
|
||||||
|
RestCinemaRepository(
|
||||||
|
MyServerService.getInstance(),
|
||||||
|
cinemaRepository,
|
||||||
|
sessionRepository,
|
||||||
|
remoteKeyRepository,
|
||||||
|
AppDatabase.getInstance(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val sessionRestRepository: RestSessionRepository by lazy {
|
||||||
|
RestSessionRepository(
|
||||||
|
MyServerService.getInstance(),
|
||||||
|
sessionRepository,
|
||||||
|
userSessionRepository,
|
||||||
|
orderSessionRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val userRestRepository: RestUserRepository by lazy {
|
||||||
|
RestUserRepository(
|
||||||
|
MyServerService.getInstance(),
|
||||||
|
userRepository,
|
||||||
|
userSessionRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val orderRestRepository: RestOrderRepository by lazy {
|
||||||
|
RestOrderRepository(
|
||||||
|
MyServerService.getInstance(),
|
||||||
|
orderRepository,
|
||||||
|
cinemaRepository,
|
||||||
|
orderSessionRepository,
|
||||||
|
remoteKeyRepository,
|
||||||
|
AppDatabase.getInstance(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val userSessionRestRepository: RestUserSessionRepository by lazy {
|
||||||
|
RestUserSessionRepository(
|
||||||
|
MyServerService.getInstance(),
|
||||||
|
userSessionRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val orderSessionRestRepository: RestOrderSessionRepository by lazy {
|
||||||
|
RestOrderSessionRepository(
|
||||||
|
MyServerService.getInstance(),
|
||||||
|
orderSessionRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
package com.example.myapplication.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
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.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 com.example.myapplication.database.remotekeys.dao.RemoteKeysDao
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeys
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [
|
||||||
|
Cinema::class,
|
||||||
|
Session::class,
|
||||||
|
Order::class,
|
||||||
|
OrderSessionCrossRef::class,
|
||||||
|
User::class,
|
||||||
|
UserSessionCrossRef::class,
|
||||||
|
RemoteKeys::class
|
||||||
|
],
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
|
@TypeConverters(LocalDateTimeConverter::class)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun cinemaDao(): CinemaDao
|
||||||
|
abstract fun sessionDao(): SessionDao
|
||||||
|
abstract fun orderDao(): OrderDao
|
||||||
|
abstract fun orderSessionCrossRefDao(): OrderSessionCrossRefDao
|
||||||
|
abstract fun userDao(): UserDao
|
||||||
|
abstract fun userSessionCrossRefDao(): UserSessionCrossRefDao
|
||||||
|
abstract fun remoteKeysDao(): RemoteKeysDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DB_NAME: String = "pmy-db"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: AppDatabase? = null
|
||||||
|
|
||||||
|
private suspend fun populateDatabase() {
|
||||||
|
INSTANCE?.let { database ->
|
||||||
|
// Users
|
||||||
|
val userDao = database.userDao()
|
||||||
|
val user1 = User(1, "login", "password")
|
||||||
|
userDao.insert(user1)
|
||||||
|
/*// Cinemas
|
||||||
|
val cinemaDao = database.cinemaDao()
|
||||||
|
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)
|
||||||
|
val order2 = Order(2, 1)
|
||||||
|
val order3 = Order(3, 1)
|
||||||
|
val order4 = Order(4, 1)
|
||||||
|
orderDao.insert(order1)
|
||||||
|
orderDao.insert(order2)
|
||||||
|
orderDao.insert(order3)
|
||||||
|
orderDao.insert(order4)
|
||||||
|
// Sessions
|
||||||
|
val sessionDao = database.sessionDao()
|
||||||
|
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(), 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) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
// UserSessions
|
||||||
|
val userSessionCrossRefDao = database.userSessionCrossRefDao()
|
||||||
|
val userSessionCrossRef1 = UserSessionCrossRef(1, 1, 5)
|
||||||
|
val userSessionCrossRef2 = UserSessionCrossRef(1, 3, 15)
|
||||||
|
userSessionCrossRefDao.insert(userSessionCrossRef1)
|
||||||
|
userSessionCrossRefDao.insert(userSessionCrossRef2)*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(appContext: Context): AppDatabase {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
Room.databaseBuilder(
|
||||||
|
appContext,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
DB_NAME
|
||||||
|
)
|
||||||
|
.addCallback(object : Callback() {
|
||||||
|
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||||
|
super.onCreate(db)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
populateDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.also { INSTANCE = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createColoredImage(color: Int): ByteArray {
|
||||||
|
val width = 100
|
||||||
|
val height = 100
|
||||||
|
|
||||||
|
val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
bmp.eraseColor(color)
|
||||||
|
|
||||||
|
val stream = ByteArrayOutputStream()
|
||||||
|
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
||||||
|
|
||||||
|
return stream.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateCinemaName(index: Int): String {
|
||||||
|
val base = 'a'.code
|
||||||
|
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.cinemaRestRepository)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
CinemaEditViewModel(
|
||||||
|
this.createSavedStateHandle(),
|
||||||
|
cinemaApplication().container.cinemaRestRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
CinemaViewModel(
|
||||||
|
this.createSavedStateHandle(),
|
||||||
|
cinemaApplication().container.cinemaRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
SessionListViewModel(
|
||||||
|
cinemaApplication().container.sessionRestRepository,
|
||||||
|
cinemaApplication().container.userSessionRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
SessionEditViewModel(
|
||||||
|
this.createSavedStateHandle(),
|
||||||
|
cinemaApplication().container.sessionRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
CartViewModel(
|
||||||
|
cinemaApplication().container.userSessionRestRepository,
|
||||||
|
cinemaApplication().container.orderRestRepository,
|
||||||
|
cinemaApplication().container.orderSessionRestRepository,
|
||||||
|
cinemaApplication().container.userRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
OrderListViewModel(
|
||||||
|
cinemaApplication().container.orderRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
OrderViewModel(
|
||||||
|
this.createSavedStateHandle(),
|
||||||
|
cinemaApplication().container.orderRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CreationExtras.cinemaApplication(): CinemaApplication =
|
||||||
|
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as CinemaApplication)
|
@ -0,0 +1,70 @@
|
|||||||
|
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 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.delay
|
||||||
|
|
||||||
|
class CartViewModel(
|
||||||
|
private val userSessionRepository: UserSessionRepository,
|
||||||
|
private val orderRepository: OrderRepository,
|
||||||
|
private val orderSessionRepository: OrderSessionRepository,
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
private val userUid: Int = 1
|
||||||
|
var cartUiState by mutableStateOf(CartUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun refreshState() {
|
||||||
|
val cart = userRepository.getCartByUser(userUid)
|
||||||
|
cartUiState = CartUiState(cart)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
refreshState()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeFromCart(user: Int, session: Session, count: Int = 1) {
|
||||||
|
userSessionRepository.deleteUserSession(UserSessionCrossRef(user, session.uid, count))
|
||||||
|
refreshState()
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
refreshState()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CartUiState(val sessionList: List<SessionFromCart> = listOf())
|
@ -0,0 +1,187 @@
|
|||||||
|
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.cinemaListUiState.collectAsLazyPagingItems()
|
||||||
|
|
||||||
|
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,23 @@
|
|||||||
|
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() {
|
||||||
|
val cinemaListUiState: Flow<PagingData<Cinema>> = cinemaRepository.getAllCinemas()
|
||||||
|
|
||||||
|
suspend fun deleteCinema(cinema: Cinema) {
|
||||||
|
cinemaRepository.deleteCinema(cinema)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
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.LaunchedEffect
|
||||||
|
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 = viewModel.cinemaUiState
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.refreshState()
|
||||||
|
}
|
||||||
|
|
||||||
|
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(viewModel, navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.example.myapplication.database.entities.composeui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.example.myapplication.database.entities.model.CinemaWithSessions
|
||||||
|
import com.example.myapplication.database.entities.repository.CinemaRepository
|
||||||
|
|
||||||
|
class CinemaViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle, private val cinemaRepository: CinemaRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
|
||||||
|
var cinemaUiState by mutableStateOf(CinemaUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun refreshState() {
|
||||||
|
if (cinemaUid > 0) {
|
||||||
|
cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init {
|
||||||
|
// viewModelScope.launch {
|
||||||
|
// if (cinemaUid > 0) {
|
||||||
|
// cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// val cinemaUiState: mutableStateOf(CinemaUiState()) = cinemaRepository.getCinema(
|
||||||
|
// cinemaUid
|
||||||
|
// ).map
|
||||||
|
// {
|
||||||
|
// CinemaUiState(it)
|
||||||
|
// }.stateIn(
|
||||||
|
// scope = viewModelScope,
|
||||||
|
// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
||||||
|
// initialValue = CinemaUiState()
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CinemaUiState(val cinemaWithSessions: CinemaWithSessions? = null)
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.example.myapplication.database.entities.composeui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
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.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
import androidx.paging.compose.itemContentType
|
||||||
|
import androidx.paging.compose.itemKey
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.ui.theme.PmudemoTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderList(
|
||||||
|
navController: NavController?,
|
||||||
|
userId: Int?,
|
||||||
|
viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val ordersUiState = viewModel.orderListUiState.collectAsLazyPagingItems()
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(all = 10.dp)
|
||||||
|
) {
|
||||||
|
items(count = ordersUiState.itemCount,
|
||||||
|
key = ordersUiState.itemKey(),
|
||||||
|
contentType = ordersUiState.itemContentType()) { index ->
|
||||||
|
val order = ordersUiState[index]
|
||||||
|
val orderId = Screen.OrderView.route.replace("{id}", order!!.uid.toString())
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 10.dp)
|
||||||
|
.clickable { navController?.navigate(orderId) }
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text("Заказ №${order.uid}", 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 OrderListPreview() {
|
||||||
|
PmudemoTheme {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
OrderList(navController = null, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
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.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
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.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class OrderListViewModel(
|
||||||
|
private val orderRepository: OrderRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
val orderListUiState: Flow<PagingData<Order>> = orderRepository.getAllOrders(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class OrderListUiState(val orderList: List<Order> = listOf())
|
@ -0,0 +1,114 @@
|
|||||||
|
package com.example.myapplication.database.entities.composeui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.example.myapplication.ui.theme.PmudemoTheme
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
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(orderUiState.sessionList) { session ->
|
||||||
|
val count = remember { mutableStateOf(session.count) }
|
||||||
|
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 (session.cinema.image != null)
|
||||||
|
Image(
|
||||||
|
bitmap = BitmapFactory.decodeByteArray(
|
||||||
|
session.cinema.image,
|
||||||
|
0,
|
||||||
|
session.cinema.image.size
|
||||||
|
).asImageBitmap(),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(90.dp)
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(start = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${session.cinema.name}, ${session.cinema.year}\n" +
|
||||||
|
"Цена: ${session.frozenPrice}\n" +
|
||||||
|
"Количество: ${count.value}",
|
||||||
|
color = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun OrderViewPreview() {
|
||||||
|
PmudemoTheme {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
OrderView(id = 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.AppContainer
|
||||||
|
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.flow
|
||||||
|
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> = flow{emit(orderRepository.getOrder(orderUid))} .map {
|
||||||
|
OrderUiState(it)
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppContainer.TIMEOUT),
|
||||||
|
initialValue = OrderUiState()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class OrderUiState(val sessionList: List<SessionFromOrder> = listOf())
|
@ -0,0 +1,144 @@
|
|||||||
|
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 kotlinx.coroutines.launch
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SessionList(
|
||||||
|
cinemaWithSessionsViewModel: CinemaViewModel,
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: SessionListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val cinemaWithSessions = cinemaWithSessionsViewModel.cinemaUiState.cinemaWithSessions!!
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if (session.availableCount != 0)
|
||||||
|
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)
|
||||||
|
cinemaWithSessionsViewModel.refreshState()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
package com.example.myapplication.database.entities.composeui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
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.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.example.myapplication.datastore.DataStoreManager
|
||||||
|
import com.example.myapplication.datastore.SettingData
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserProfile(
|
||||||
|
isDarkTheme: MutableState<Boolean>,
|
||||||
|
dataStoreManager: DataStoreManager
|
||||||
|
) {
|
||||||
|
var username by remember { mutableStateOf("") }
|
||||||
|
var password by remember { mutableStateOf("") }
|
||||||
|
var isRegistration by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Логин",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
BasicTextField(
|
||||||
|
value = username,
|
||||||
|
onValueChange = { newValue -> username = newValue },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size(36.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.secondary, RoundedCornerShape(18.dp))
|
||||||
|
.padding(start = 13.dp, top = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Пароль",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
BasicTextField(
|
||||||
|
value = password,
|
||||||
|
onValueChange = { newValue -> password = newValue },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size(36.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.secondary, RoundedCornerShape(18.dp))
|
||||||
|
.padding(start = 13.dp, top = 8.dp),
|
||||||
|
visualTransformation = PasswordVisualTransformation()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isRegistration) {
|
||||||
|
Button(
|
||||||
|
onClick = { },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Text("Регистрация")
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Уже есть аккаунт? Войти",
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
isRegistration = false
|
||||||
|
}
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
onClick = { },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Text("Вход")
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Нет аккаунта? Зарегистрироваться",
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
isRegistration = true
|
||||||
|
}
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val switchColors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = MaterialTheme.colorScheme.primary, // Change the color when the switch is checked
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.secondary, // Change the color of the track when the switch is checked
|
||||||
|
uncheckedThumbColor = MaterialTheme.colorScheme.primary, // Change the color when the switch is unchecked
|
||||||
|
uncheckedTrackColor = MaterialTheme.colorScheme.onPrimary // Change the color of the track when the switch is unchecked
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Темная тема", modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.padding(5.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
val coroutine = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = isDarkTheme.value,
|
||||||
|
onCheckedChange = {
|
||||||
|
isDarkTheme.value = !isDarkTheme.value
|
||||||
|
coroutine.launch {
|
||||||
|
dataStoreManager.saveSettings(SettingData(isDarkTheme = isDarkTheme.value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = switchColors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun UserProfilePreview() {
|
||||||
|
PmudemoTheme {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
UserProfile(navController = null, isDarkTheme = remember { mutableStateOf(true) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
@ -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,99 @@
|
|||||||
|
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)
|
||||||
|
.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,108 @@
|
|||||||
|
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)
|
||||||
|
.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(): 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, c.uid as cinema_id " +
|
||||||
|
"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(vararg cinema: Cinema)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(cinema: Cinema)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(cinema: Cinema)
|
||||||
|
|
||||||
|
@Query("DELETE FROM cinemas")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
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.Order
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface OrderDao {
|
||||||
|
@Query("select * from orders where user_id = :userId")
|
||||||
|
fun getAll(userId: Int?): PagingSource<Int, 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(vararg order: Order): List<Long>
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(order: Order)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(order: Order)
|
||||||
|
|
||||||
|
@Query("DELETE FROM orders")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.database.entities.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface OrderSessionCrossRefDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insert(orderSessionCrossRef: OrderSessionCrossRef)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(orderSessionCrossRef: OrderSessionCrossRef)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(orderSessionCrossRef: OrderSessionCrossRef)
|
||||||
|
|
||||||
|
@Query("DELETE FROM orders_sessions where orders_sessions.order_id = :orderId")
|
||||||
|
suspend fun deleteByOrderUid(orderId: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM orders_sessions where orders_sessions.session_id = :sessionId")
|
||||||
|
suspend fun deleteBySessionUid(sessionId: Int)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
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.Session
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface SessionDao {
|
||||||
|
@Query("select * from sessions where sessions.uid = :uid")
|
||||||
|
suspend fun getByUid(uid: Int): Session
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(vararg session: Session)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(session: Session)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(session: Session)
|
||||||
|
|
||||||
|
@Query("DELETE FROM sessions")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT s.max_count-IFNULL(SUM(os.count), 0) as available_count " +
|
||||||
|
"FROM sessions AS s " +
|
||||||
|
"LEFT JOIN orders_sessions AS os ON os.session_id = s.uid " +
|
||||||
|
"WHERE s.uid = :sessionId " +
|
||||||
|
"GROUP BY s.uid"
|
||||||
|
)
|
||||||
|
suspend fun getAvailableCountOfSession(sessionId: Int): Int
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
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.SessionFromCart
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserDao {
|
||||||
|
@Query("select * from users order by login collate nocase asc")
|
||||||
|
fun getAll(): Flow<List<User>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"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 " +
|
||||||
|
"left join orders_sessions on sessions.uid = orders_sessions.session_id " +
|
||||||
|
"where users_sessions.user_id = :userId " +
|
||||||
|
"GROUP BY users_sessions.session_id "
|
||||||
|
)
|
||||||
|
suspend fun getCartByUid(userId: Int): List<SessionFromCart>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(vararg user: User)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(user: User)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(user: User)
|
||||||
|
|
||||||
|
@Query("DELETE FROM users")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.database.entities.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.entities.model.UserSessionCrossRef
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserSessionCrossRefDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insert(userSessionCrossRef: UserSessionCrossRef)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(userSessionCrossRef: UserSessionCrossRef)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(userSessionCrossRef: UserSessionCrossRef)
|
||||||
|
|
||||||
|
@Query("DELETE FROM users_sessions where users_sessions.user_id = :userId")
|
||||||
|
suspend fun deleteByUserUid(userId: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM users_sessions where users_sessions.session_id = :sessionId")
|
||||||
|
suspend fun deleteBySessionUid(sessionId: Int)
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "cinemas")
|
||||||
|
data class Cinema(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val uid: Int = 0,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
|
val image: ByteArray?,
|
||||||
|
val year: Long
|
||||||
|
) {
|
||||||
|
@Ignore
|
||||||
|
constructor(
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
image: ByteArray?,
|
||||||
|
year: Long
|
||||||
|
) : 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 image.contentEquals(other.image)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
class LocalDateTimeConverter {
|
||||||
|
@TypeConverter
|
||||||
|
fun fromLocalDateTime(value: LocalDateTime?): String? {
|
||||||
|
return value?.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toLocalDateTime(value: String?): LocalDateTime? {
|
||||||
|
return value?.let { LocalDateTime.parse(it) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "orders", foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = User::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["user_id"],
|
||||||
|
onDelete = ForeignKey.RESTRICT,
|
||||||
|
onUpdate = ForeignKey.RESTRICT
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Order(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val uid: Int,
|
||||||
|
@ColumnInfo(name = "user_id", index = true)
|
||||||
|
val userId: Int?,
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as Order
|
||||||
|
if (uid != other.uid) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import java.util.Objects
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "orders_sessions",
|
||||||
|
primaryKeys = ["order_id", "session_id"]
|
||||||
|
)
|
||||||
|
data class OrderSessionCrossRef(
|
||||||
|
@ColumnInfo(name = "order_id", index = true)
|
||||||
|
val orderId: Int,
|
||||||
|
@ColumnInfo(name = "session_id", index = true)
|
||||||
|
val sessionId: Int,
|
||||||
|
@ColumnInfo(name = "frozen_price")
|
||||||
|
val frozenPrice: Double,
|
||||||
|
val count: Int
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (javaClass != other?.javaClass) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
other as OrderSessionCrossRef
|
||||||
|
if (orderId == other.orderId && sessionId == other.sessionId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(orderId, sessionId)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "sessions", foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = Cinema::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["cinema_id"],
|
||||||
|
onDelete = ForeignKey.CASCADE,
|
||||||
|
onUpdate = ForeignKey.RESTRICT
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Session(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
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 = 0,
|
||||||
|
) {
|
||||||
|
@Ignore
|
||||||
|
constructor(
|
||||||
|
dateTime: LocalDateTime,
|
||||||
|
price: Double,
|
||||||
|
maxCount: Int,
|
||||||
|
cinema: Cinema,
|
||||||
|
) : 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Relation
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
data class SessionFromCart(
|
||||||
|
@ColumnInfo(name = "uid")
|
||||||
|
val uid: Int = 0,
|
||||||
|
@ColumnInfo(name = "date_time")
|
||||||
|
val dateTime: LocalDateTime,
|
||||||
|
val price: Double,
|
||||||
|
@ColumnInfo(name = "available_count")
|
||||||
|
val availableCount: Int,
|
||||||
|
val count: Int,
|
||||||
|
@ColumnInfo(name = "cinema_id")
|
||||||
|
val cinemaId: Int = 0,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "cinema_id",
|
||||||
|
entity = Cinema::class,
|
||||||
|
entityColumn = "uid"
|
||||||
|
) val cinema: Cinema
|
||||||
|
)
|
@ -0,0 +1,47 @@
|
|||||||
|
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,
|
||||||
|
@ColumnInfo(name = "cinema_id")
|
||||||
|
val cinemaId: 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
|
||||||
|
if (cinemaId != other.cinemaId) 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()
|
||||||
|
result = 31 * result + cinemaId.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SessionFromCinema.toSession(): Session = Session (
|
||||||
|
uid,
|
||||||
|
dateTime,
|
||||||
|
price,
|
||||||
|
availableCount,
|
||||||
|
cinemaId
|
||||||
|
)
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Relation
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
data class SessionFromOrder(
|
||||||
|
@ColumnInfo(name = "uid")
|
||||||
|
val uid: Int = 0,
|
||||||
|
@ColumnInfo(name = "date_time")
|
||||||
|
val dateTime: LocalDateTime,
|
||||||
|
@ColumnInfo(name = "frozen_price")
|
||||||
|
val frozenPrice: Double,
|
||||||
|
val count: Int,
|
||||||
|
@ColumnInfo(name = "cinema_id")
|
||||||
|
val cinemaId: Int = 0,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "cinema_id",
|
||||||
|
entity = Cinema::class,
|
||||||
|
entityColumn = "uid"
|
||||||
|
)
|
||||||
|
val cinema: Cinema
|
||||||
|
)
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "users")
|
||||||
|
data class User(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val uid: Int = 0,
|
||||||
|
val login: String,
|
||||||
|
val password: String
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as User
|
||||||
|
if (uid != other.uid) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uid ?: -1
|
||||||
|
}
|
||||||
|
}
|
@ -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.PagingData
|
||||||
|
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<PagingData<Cinema>>
|
||||||
|
suspend fun getCinema(uid: Int): CinemaWithSessions
|
||||||
|
suspend fun insertCinema(cinema: Cinema)
|
||||||
|
suspend fun updateCinema(cinema: Cinema)
|
||||||
|
suspend fun deleteCinema(cinema: Cinema)
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.example.myapplication.database.entities.repository
|
||||||
|
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import com.example.myapplication.database.AppContainer
|
||||||
|
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.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class OfflineCinemaRepository(private val cinemaDao: CinemaDao) : CinemaRepository {
|
||||||
|
override fun getAllCinemas(): Flow<PagingData<Cinema>> = Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
pagingSourceFactory = cinemaDao::getAll
|
||||||
|
).flow
|
||||||
|
|
||||||
|
override suspend fun getCinema(uid: Int): CinemaWithSessions {
|
||||||
|
val item = cinemaDao.getByUid(uid)
|
||||||
|
.map { map ->
|
||||||
|
map.firstNotNullOf {
|
||||||
|
CinemaWithSessions(
|
||||||
|
cinema = it.key,
|
||||||
|
sessions = it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.first()
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
fun getAllCinemasPagingSource(): PagingSource<Int, Cinema> = cinemaDao.getAll()
|
||||||
|
|
||||||
|
suspend fun insertCinemas(cinemas: List<Cinema>) =
|
||||||
|
cinemaDao.insert(*cinemas.toTypedArray())
|
||||||
|
|
||||||
|
suspend fun clearCinemas() = cinemaDao.deleteAll()
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.example.myapplication.database.entities.repository
|
||||||
|
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import com.example.myapplication.database.AppContainer
|
||||||
|
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<PagingData<Order>> = Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
pagingSourceFactory = { orderDao.getAll(userId) }
|
||||||
|
).flow
|
||||||
|
|
||||||
|
override suspend fun getOrder(uid: Int): List<SessionFromOrder> = orderDao.getByUid(uid)
|
||||||
|
|
||||||
|
override suspend fun insertOrder(order: Order): Long = orderDao.insert(order).first()
|
||||||
|
|
||||||
|
fun getAllOrdersPagingSource(userId: Int?): PagingSource<Int, Order> = orderDao.getAll(userId)
|
||||||
|
|
||||||
|
suspend fun clearOrders() = orderDao.deleteAll()
|
||||||
|
|
||||||
|
suspend fun insertOrders(orders: List<Order>) = orderDao.insert(*orders.toTypedArray())
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
suspend fun deleteOrderSessions(userId: Int) = orderSessionDao.deleteByOrderUid(userId)
|
||||||
|
|
||||||
|
suspend fun deleteSessionsByUid(sessionId: Int) = orderSessionDao.deleteBySessionUid(sessionId)
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.example.myapplication.database.entities.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.entities.dao.SessionDao
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
|
||||||
|
class OfflineSessionRepository(private val sessionDao: SessionDao) : SessionRepository {
|
||||||
|
override suspend fun getSession(uid: Int): 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)
|
||||||
|
|
||||||
|
suspend fun insertSessions(sessions: List<Session>) =
|
||||||
|
sessionDao.insert(*sessions.toTypedArray())
|
||||||
|
|
||||||
|
suspend fun clearSessions() = sessionDao.deleteAll()
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
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 suspend fun getCartByUser(userId: Int): 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)
|
||||||
|
|
||||||
|
suspend fun insertUsers(users: List<User>) =
|
||||||
|
userDao.insert(*users.toTypedArray())
|
||||||
|
|
||||||
|
suspend fun clearUsers() = userDao.deleteAll()
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
suspend fun deleteSessionsByUid(sessionId: Int) = userSessionDao.deleteBySessionUid(sessionId)
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.example.myapplication.database.entities.repository
|
||||||
|
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
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<PagingData<Order>>
|
||||||
|
suspend fun getOrder(uid: Int): List<SessionFromOrder>
|
||||||
|
suspend fun insertOrder(order: Order): Long
|
||||||
|
}
|
@ -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 {
|
||||||
|
suspend fun getSession(uid: Int): 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>>
|
||||||
|
suspend fun getCartByUser(userId: Int): 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)
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.myapplication.database.remotekeys.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeys
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RemoteKeysDao {
|
||||||
|
@Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type")
|
||||||
|
suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertAll(remoteKey: List<RemoteKeys>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM remote_keys WHERE type = :type")
|
||||||
|
suspend fun clearRemoteKeys(type: RemoteKeyType)
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.example.myapplication.database.remotekeys.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
|
||||||
|
enum class RemoteKeyType(private val type: String) {
|
||||||
|
CINEMA(Cinema::class.simpleName ?: "Cinema"),
|
||||||
|
ORDER(Order::class.simpleName ?: "Order"),
|
||||||
|
SESSION(Session::class.simpleName ?: "Session");
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(tableName = "remote_keys")
|
||||||
|
data class RemoteKeys(
|
||||||
|
@PrimaryKey val entityId: Int,
|
||||||
|
@TypeConverters(RemoteKeyType::class)
|
||||||
|
val type: RemoteKeyType,
|
||||||
|
val prevKey: Int?,
|
||||||
|
val nextKey: Int?
|
||||||
|
)
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.example.myapplication.database.remotekeys.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.remotekeys.dao.RemoteKeysDao
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeys
|
||||||
|
|
||||||
|
class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository {
|
||||||
|
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
|
||||||
|
remoteKeysDao.getRemoteKeys(id, type)
|
||||||
|
|
||||||
|
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>) =
|
||||||
|
remoteKeysDao.insertAll(remoteKeys)
|
||||||
|
|
||||||
|
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
|
||||||
|
remoteKeysDao.clearRemoteKeys(type)
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.example.myapplication.database.remotekeys.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
|
||||||
|
import com.example.myapplication.database.remotekeys.model.RemoteKeys
|
||||||
|
|
||||||
|
interface RemoteKeyRepository {
|
||||||
|
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
|
||||||
|
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>)
|
||||||
|
suspend fun deleteRemoteKey(type: RemoteKeyType)
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.example.myapplication.datastore
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("data_store")
|
||||||
|
|
||||||
|
class DataStoreManager(private val context: Context) {
|
||||||
|
suspend fun saveSettings(settingData: SettingData) {
|
||||||
|
context.dataStore.edit { pref ->
|
||||||
|
pref[booleanPreferencesKey("isDarkTheme")] = settingData.isDarkTheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSettings() = context.dataStore.data.map { pref ->
|
||||||
|
return@map SettingData(
|
||||||
|
pref[booleanPreferencesKey("isDarkTheme")] ?: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.example.myapplication.datastore
|
||||||
|
|
||||||
|
data class SettingData(
|
||||||
|
val isDarkTheme: Boolean
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.myapplication.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val LightGray = Color(0xFFB2CCD6)
|
||||||
|
val LightBlueGray = Color(0xFF70A3B2)
|
||||||
|
val LightBgGray = Color(0xFFCED6DC)
|
||||||
|
|
||||||
|
val Gray = Color(0xFFD6D6D6)
|
||||||
|
val DarkGray = Color(0xFF191A1F)
|
||||||
|
val BgGray = Color(0xFF2A2D32)
|
@ -10,21 +10,31 @@ import androidx.compose.material3.dynamicLightColorScheme
|
|||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
primary = Purple80,
|
primary = DarkGray,
|
||||||
secondary = PurpleGrey80,
|
onPrimary = Color.White,
|
||||||
tertiary = Pink80
|
|
||||||
|
secondary = Gray,
|
||||||
|
onSecondary = Color.Black,
|
||||||
|
|
||||||
|
background = BgGray,
|
||||||
|
onBackground = Color.White,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = Purple40,
|
primary = LightBlueGray,
|
||||||
secondary = PurpleGrey40,
|
onPrimary = Color.White,
|
||||||
tertiary = Pink40
|
|
||||||
|
secondary = LightGray,
|
||||||
|
onSecondary = Color.Black,
|
||||||
|
|
||||||
|
onBackground = Color.Black,
|
||||||
|
|
||||||
/* Other default colors to override
|
/* Other default colors to override
|
||||||
background = Color(0xFFFFFBFE),
|
background = Color(0xFFFFFBFE),
|
||||||
@ -38,10 +48,10 @@ private val LightColorScheme = lightColorScheme(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MyApplicationTheme(
|
fun PmudemoTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
// Dynamic color is available on Android 12+
|
// Dynamic color is available on Android 12+
|
||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = false,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
@ -1,11 +0,0 @@
|
|||||||
package com.example.myapplication.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
|
||||||
val Pink40 = Color(0xFF7D5260)
|
|
9
app/src/main/res/drawable/minus.xml
Normal file
9
app/src/main/res/drawable/minus.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M19,13H5v-2h14v2z" />
|
||||||
|
</vector>
|
BIN
app/src/main/res/drawable/photo.jpg
Normal file
BIN
app/src/main/res/drawable/photo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
@ -1,3 +1,22 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">My Application</string>
|
<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>
|
||||||
|
<string name="Cinema_description">Описание</string>
|
||||||
|
<string name="Cinema_image">Изображение</string>
|
||||||
|
<string name="Cart_title">Корзина</string>
|
||||||
|
<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>
|
</resources>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.MyApplication" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.Pmudemo" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
</resources>
|
</resources>
|
7
app/src/main/res/xml/network_security_config.xml
Normal file
7
app/src/main/res/xml/network_security_config.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<!-- <domain includeSubdomains="true">192.168.154.166</domain>-->
|
||||||
|
<domain includeSubdomains="true">192.168.0.101</domain>
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
@ -1,9 +1,8 @@
|
|||||||
package com.example.myapplication
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
*
|
*
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.1.1" apply false
|
id("com.android.application") version "8.1.2" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
|
||||||
|
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
|
||||||
|
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.20" apply false
|
||||||
}
|
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,4 +1,4 @@
|
|||||||
#Mon Sep 18 13:48:19 GMT+04:00 2023
|
#Sun Oct 01 15:07:08 GMT+04:00 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||||
|
BIN
reports/2 - Балберова Дарья Николаевна.docx
Normal file
BIN
reports/2 - Балберова Дарья Николаевна.docx
Normal file
Binary file not shown.
BIN
reports/3 - Балберова Дарья Николаевна.docx
Normal file
BIN
reports/3 - Балберова Дарья Николаевна.docx
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user