Compare commits
35 Commits
master
...
coursework
Author | SHA1 | Date | |
---|---|---|---|
3619e27dec | |||
5e33722852 | |||
ff58cccfc5 | |||
93f4af21a9 | |||
d35ee4907d | |||
c8f8b59672 | |||
4bf7ebb6bf | |||
f11ebc82f5 | |||
c4c941c0c9 | |||
01bc335c4f | |||
7fbe49527a | |||
4b84248c66 | |||
39f2cf609c | |||
a66218c7ab | |||
a14249d6ef | |||
37f7fbaf9e | |||
1e1dd76b7b | |||
1a72d6d368 | |||
5e992dbf8a | |||
baf333dea1 | |||
30f50e94c7 | |||
c0b94141a3 | |||
42c21b0ce7 | |||
55a0ac8581 | |||
3dc39aa82f | |||
aab18402b5 | |||
d45b6d7ed8 | |||
d709367774 | |||
28fd92559c | |||
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,39 +25,74 @@ 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 {
|
||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buildToolsVersion = "34.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
|
// LiveData
|
||||||
|
implementation("androidx.compose.runtime:runtime-livedata:1.5.4")
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
11
app/src/main/java/com/example/myapplication/LiveStore.kt
Normal file
11
app/src/main/java/com/example/myapplication/LiveStore.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
|
||||||
|
class LiveStore {
|
||||||
|
companion object {
|
||||||
|
val user = MutableLiveData<User?>(null)
|
||||||
|
val searchRequest = MutableLiveData("")
|
||||||
|
}
|
||||||
|
}
|
@ -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,48 @@
|
|||||||
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
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.Authenticator
|
||||||
|
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")
|
||||||
|
appContext = applicationContext
|
||||||
|
setContent {
|
||||||
|
PmudemoTheme(darkTheme = isDarkTheme.value) {
|
||||||
|
LaunchedEffect(key1 = true) {
|
||||||
|
dataStoreManager.getDarkTheme().collect { setting ->
|
||||||
|
isDarkTheme.value = setting == "Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
Authenticator(dataStoreManager)
|
||||||
|
MainNavbar(
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
dataStoreManager = dataStoreManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
lateinit var appContext: Context
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
enum class ApiStatus { LOADING, ERROR, DONE }
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
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,194 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
import com.example.myapplication.api.cinema.CinemaRemote
|
||||||
|
import com.example.myapplication.api.cinema.CinemaWithSessionsRemote
|
||||||
|
import com.example.myapplication.api.order.OrderRemote
|
||||||
|
import com.example.myapplication.api.order.OrderWithUserRemote
|
||||||
|
import com.example.myapplication.api.session.ReportRemote
|
||||||
|
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.example.myapplication.api.user.UserSessionRemote
|
||||||
|
import com.example.myapplication.api.user.UserSessionWithSessionRemote
|
||||||
|
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
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
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
interface MyServerService {
|
||||||
|
@GET("orders")
|
||||||
|
suspend fun getOrders(): List<OrderRemote>
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
@GET("cinemas/{id}?_embed=sessions")
|
||||||
|
suspend fun getCinemaWithSessions(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): CinemaWithSessionsRemote
|
||||||
|
|
||||||
|
@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("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("userssessions?_expand=session")
|
||||||
|
suspend fun getUserCart(
|
||||||
|
@Query("userId") userId: Int,
|
||||||
|
): List<UserSessionWithSessionRemote>
|
||||||
|
|
||||||
|
@GET("userssessions")
|
||||||
|
suspend fun getUserSessions(
|
||||||
|
@Query("userId") userId: Int,
|
||||||
|
): List<UserSessionRemote>
|
||||||
|
|
||||||
|
@GET("userssessions?_expand=session")
|
||||||
|
suspend fun getUsersSessions(): List<UserSessionWithSessionRemote>
|
||||||
|
|
||||||
|
@DELETE("userssessions/{id}")
|
||||||
|
suspend fun deleteUserSession(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): UserSessionRemote
|
||||||
|
|
||||||
|
@GET("users?_limit=1")
|
||||||
|
suspend fun getUser(
|
||||||
|
@Query("login") login: String,
|
||||||
|
): List<UserRemote>
|
||||||
|
|
||||||
|
@GET("userssessions?_limit=1")
|
||||||
|
suspend fun getUserSession(
|
||||||
|
@Query("userId") userId: Int,
|
||||||
|
@Query("sessionId") sessionId: Int,
|
||||||
|
): List<UserSessionRemote>
|
||||||
|
|
||||||
|
@POST("userssessions")
|
||||||
|
suspend fun createUserSession(
|
||||||
|
@Body userSessionRemote: UserSessionRemote,
|
||||||
|
): UserSessionRemote
|
||||||
|
|
||||||
|
@PUT("userssessions/{id}")
|
||||||
|
suspend fun updateUserCart(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body userSessionRemote: UserSessionRemote,
|
||||||
|
): UserSessionRemote
|
||||||
|
|
||||||
|
@POST("users")
|
||||||
|
suspend fun createUser(
|
||||||
|
@Body user: UserRemote,
|
||||||
|
): UserRemote
|
||||||
|
|
||||||
|
@GET("orders")
|
||||||
|
suspend fun getOrders(
|
||||||
|
@Query("userId") userId: Int,
|
||||||
|
@Query("_page") page: Int,
|
||||||
|
@Query("_limit") limit: Int,
|
||||||
|
): List<OrderRemote>
|
||||||
|
|
||||||
|
@GET("orders?_expand=user")
|
||||||
|
suspend fun getOrders(
|
||||||
|
@Query("_page") page: Int,
|
||||||
|
@Query("_limit") limit: Int,
|
||||||
|
): List<OrderWithUserRemote>
|
||||||
|
|
||||||
|
@GET("orders/{id}")
|
||||||
|
suspend fun getOrder(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
): OrderRemote
|
||||||
|
|
||||||
|
@POST("orders")
|
||||||
|
suspend fun createOrder(
|
||||||
|
@Body order: OrderRemote,
|
||||||
|
): OrderRemote
|
||||||
|
|
||||||
|
@PUT("orders/{id}")
|
||||||
|
suspend fun updateOrder(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body orderRemote: OrderRemote,
|
||||||
|
): OrderRemote
|
||||||
|
|
||||||
|
@GET("report")
|
||||||
|
suspend fun getReport(
|
||||||
|
@Query("startDate") startDate: Date,
|
||||||
|
@Query("endDate") endDate: Date
|
||||||
|
): ReportRemote
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
//private const val BASE_URL = "http://192.168.154.166:8080/"
|
||||||
|
private const val BASE_URL = "http://192.168.0.101:8079/"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: MyServerService? = null
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
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,112 @@
|
|||||||
|
package com.example.myapplication.api.cinema
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
|
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.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 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)
|
||||||
|
}
|
||||||
|
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
return MediatorResult.Error(exception)
|
||||||
|
} catch (exception: HttpException) {
|
||||||
|
return MediatorResult.Error(exception)
|
||||||
|
} catch (exception: SQLiteConstraintException) {
|
||||||
|
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,26 @@
|
|||||||
|
package com.example.myapplication.api.cinema
|
||||||
|
|
||||||
|
import com.example.myapplication.api.session.SessionFromCinemaRemote
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
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>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CinemaWithSessionsRemote.toCinema(): Cinema = Cinema(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
year
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,94 @@
|
|||||||
|
package com.example.myapplication.api.cinema
|
||||||
|
|
||||||
|
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.session.toSessionFromCinema
|
||||||
|
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.model.toSession
|
||||||
|
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(name: String): Flow<PagingData<Cinema>> {
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
return Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
remoteMediator = CinemaRemoteMediator(
|
||||||
|
service,
|
||||||
|
dbCinemaRepository,
|
||||||
|
dbSessionRepository,
|
||||||
|
dbRemoteKeyRepository,
|
||||||
|
database,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
dbCinemaRepository.getAllCinemasPagingSource()
|
||||||
|
} else {
|
||||||
|
dbCinemaRepository.getAllCinemasPagingSource(name)
|
||||||
|
}
|
||||||
|
}.flow
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCinema(uid: Int): CinemaWithSessions {
|
||||||
|
val cinemaWithSessions = service.getCinemaWithSessions(uid)
|
||||||
|
val orders = service.getOrders()
|
||||||
|
val sessions = cinemaWithSessions.sessions.map { sessionFromCinemaRemote ->
|
||||||
|
SessionFromCinema(
|
||||||
|
sessionFromCinemaRemote.id,
|
||||||
|
sessionFromCinemaRemote.dateTime,
|
||||||
|
sessionFromCinemaRemote.price,
|
||||||
|
sessionFromCinemaRemote.maxCount - orders.flatMap
|
||||||
|
{ order ->
|
||||||
|
order.sessions.filter { session ->
|
||||||
|
session.id == sessionFromCinemaRemote.id &&
|
||||||
|
session.cinemaId == sessionFromCinemaRemote.cinemaId &&
|
||||||
|
session.dateTime == sessionFromCinemaRemote.dateTime
|
||||||
|
}
|
||||||
|
}.sumOf { session -> session.count },
|
||||||
|
uid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dbSessionRepository.insertSessions(cinemaWithSessions.sessions.map {
|
||||||
|
it.toSessionFromCinema().toSession()
|
||||||
|
})
|
||||||
|
return CinemaWithSessions(cinemaWithSessions.toCinema(), 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.getUsersSessions()
|
||||||
|
cart.forEach { userSessionRemote ->
|
||||||
|
if (userSessionRemote.session.cinemaId == cinema.uid) {
|
||||||
|
service.deleteUserSession(userSessionRemote.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service.deleteCinema(cinema.uid)
|
||||||
|
dbCinemaRepository.deleteCinema(cinema)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.example.myapplication.api.order
|
||||||
|
|
||||||
|
import com.example.myapplication.api.session.SessionFromOrderRemote
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OrderRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val userId: Int = 0,
|
||||||
|
@Contextual
|
||||||
|
val dateTime: LocalDateTime = LocalDateTime.now(),
|
||||||
|
var sessions: List<SessionFromOrderRemote> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun OrderRemote.toOrder(): Order = Order(
|
||||||
|
id, userId, dateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
|
||||||
|
uid, userId, dateTime, sessions = emptyList()
|
||||||
|
)
|
@ -0,0 +1,126 @@
|
|||||||
|
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.LiveStore
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.user.toUser
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import com.example.myapplication.database.entities.model.OrderWithUser
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineOrderRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineUserRepository
|
||||||
|
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 dbUserRepository: OfflineUserRepository,
|
||||||
|
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||||
|
private val database: AppDatabase
|
||||||
|
) : RemoteMediator<Int, OrderWithUser>() {
|
||||||
|
|
||||||
|
override suspend fun initialize(): InitializeAction {
|
||||||
|
return InitializeAction.LAUNCH_INITIAL_REFRESH
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(
|
||||||
|
loadType: LoadType,
|
||||||
|
state: PagingState<Int, OrderWithUser>
|
||||||
|
): 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 user = LiveStore.user
|
||||||
|
var users: List<User> = emptyList()
|
||||||
|
val orders: List<Order> = if (user.value?.role == UserRole.ADMIN) {
|
||||||
|
val temp = service.getOrders(page = page, limit = state.config.pageSize)
|
||||||
|
users = temp.map { it.user.toUser() }
|
||||||
|
temp.map { it.toOrder() }
|
||||||
|
} else {
|
||||||
|
service.getOrders(
|
||||||
|
userId = user.value?.uid ?: 0, page = page, limit = state.config.pageSize
|
||||||
|
).map { it.toOrder() }
|
||||||
|
}
|
||||||
|
|
||||||
|
val endOfPaginationReached = orders.isEmpty()
|
||||||
|
database.withTransaction {
|
||||||
|
if (loadType == LoadType.REFRESH) {
|
||||||
|
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
|
||||||
|
dbUserRepository.clearUsers()
|
||||||
|
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)
|
||||||
|
dbUserRepository.insertUsers(users)
|
||||||
|
}
|
||||||
|
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, OrderWithUser>): 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, OrderWithUser>): 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, OrderWithUser>
|
||||||
|
): RemoteKeys? {
|
||||||
|
return state.anchorPosition?.let { position ->
|
||||||
|
state.closestItemToPosition(position)?.uid?.let { orderUid ->
|
||||||
|
dbRemoteKeyRepository.getAllRemoteKeys(orderUid, RemoteKeyType.ORDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.example.myapplication.api.order
|
||||||
|
|
||||||
|
import com.example.myapplication.api.session.SessionFromOrderRemote
|
||||||
|
import com.example.myapplication.api.user.UserRemote
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OrderWithUserRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val userId: Int = 0,
|
||||||
|
val user: UserRemote,
|
||||||
|
@Contextual
|
||||||
|
val dateTime: LocalDateTime = LocalDateTime.now(),
|
||||||
|
var sessions: List<SessionFromOrderRemote> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun OrderWithUserRemote.toOrder(): Order = Order(
|
||||||
|
id, userId, dateTime
|
||||||
|
)
|
@ -0,0 +1,69 @@
|
|||||||
|
package com.example.myapplication.api.order
|
||||||
|
|
||||||
|
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.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.OrderWithUser
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineOrderRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.OfflineUserRepository
|
||||||
|
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 dbUserRepository: OfflineUserRepository,
|
||||||
|
private val dbOrderSessionRepository: OfflineOrderSessionRepository,
|
||||||
|
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||||
|
private val database: AppDatabase
|
||||||
|
) : OrderRepository {
|
||||||
|
override fun getAllOrders(): Flow<PagingData<OrderWithUser>> {
|
||||||
|
val pagingSourceFactory = { dbOrderRepository.getAllOrdersPagingSource() }
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
return Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
remoteMediator = OrderRemoteMediator(
|
||||||
|
service,
|
||||||
|
dbOrderRepository,
|
||||||
|
dbUserRepository,
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertOrder(order: Order): Long {
|
||||||
|
return dbOrderRepository.insertOrder(service.createOrder(order.toOrderRemote()).toOrder())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.example.myapplication.api.ordersession
|
||||||
|
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.session.SessionFromOrderRemote
|
||||||
|
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) {
|
||||||
|
val orderRemote = service.getOrder(orderSessionCrossRef.orderId)
|
||||||
|
val session = service.getSession(orderSessionCrossRef.sessionId)
|
||||||
|
|
||||||
|
val sessionFromOrder = SessionFromOrderRemote(
|
||||||
|
session.id,
|
||||||
|
session.dateTime,
|
||||||
|
session.price,
|
||||||
|
orderSessionCrossRef.count,
|
||||||
|
session.cinemaId,
|
||||||
|
session.cinema
|
||||||
|
)
|
||||||
|
|
||||||
|
orderRemote.sessions = orderRemote.sessions.toMutableList().apply {
|
||||||
|
add(sessionFromOrder)
|
||||||
|
}
|
||||||
|
service.updateOrder(orderSessionCrossRef.orderId, orderRemote)
|
||||||
|
dbOrderSessionRepository.insertOrderSession(orderSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReportRemote(
|
||||||
|
@SerialName("report_data")
|
||||||
|
val reportData: List<SessionFromReportRemote>,
|
||||||
|
@SerialName("total_revenue")
|
||||||
|
val totalRevenue: Double = 0.0,
|
||||||
|
@SerialName("total_purchased_tickets")
|
||||||
|
val totalPurchasedTickets: Int = 0
|
||||||
|
)
|
@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
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.getUsersSessions()
|
||||||
|
cart.forEach { userSessionRemote ->
|
||||||
|
if (userSessionRemote.session.id == session.uid) {
|
||||||
|
service.deleteUserSession(userSessionRemote.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service.deleteSession(session.uid)
|
||||||
|
dbUserSessionRepository.deleteSessionsByUid(session.uid)
|
||||||
|
dbOrderSessionRepository.deleteSessionsByUid(session.uid)
|
||||||
|
dbSessionRepository.deleteSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReport(startDate: Date, endDate: Date): ReportRemote {
|
||||||
|
return service.getReport(startDate, endDate)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.example.myapplication.api.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
|
||||||
|
)
|
@ -0,0 +1,24 @@
|
|||||||
|
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,
|
||||||
|
val cinema: CinemaRemote,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SessionFromOrderRemote.toSessionFromOrder(): SessionFromOrder =
|
||||||
|
SessionFromOrder(
|
||||||
|
id, dateTime, frozenPrice, count, cinemaId, cinema.toCinema()
|
||||||
|
)
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SessionFromReportRemote(
|
||||||
|
@SerialName("cinema_name")
|
||||||
|
val cinemaName: String = "",
|
||||||
|
@Contextual
|
||||||
|
@SerialName("current_ticket_date_time")
|
||||||
|
val ticketDateTime: org.threeten.bp.LocalDateTime,
|
||||||
|
@SerialName("current_ticket_price")
|
||||||
|
val ticketPrice: Double = 0.0,
|
||||||
|
@SerialName("max_ticket_quantity")
|
||||||
|
val ticketQuantity: Int = 0,
|
||||||
|
@SerialName("purchased_tickets")
|
||||||
|
val ticketsPurchased: Int = 0,
|
||||||
|
val revenue: Double = 0.0
|
||||||
|
)
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.example.myapplication.api.session
|
||||||
|
|
||||||
|
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,72 @@
|
|||||||
|
package com.example.myapplication.api.user
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.cinema.toCinema
|
||||||
|
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 getUser(login: String): User? {
|
||||||
|
return service.getUser(login).firstOrNull()?.toUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> {
|
||||||
|
val cart = service.getUserCart(userId)
|
||||||
|
dbUserSessionRepository.deleteUserSessions(userId)
|
||||||
|
cart.map { sessionFromCartRemote ->
|
||||||
|
dbUserSessionRepository.insertUserSession(
|
||||||
|
UserSessionCrossRef(
|
||||||
|
userId,
|
||||||
|
sessionFromCartRemote.sessionId,
|
||||||
|
sessionFromCartRemote.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val orders = service.getOrders()
|
||||||
|
val sessions = cart.map { sessionFromCartRemote ->
|
||||||
|
SessionFromCart(
|
||||||
|
uid = sessionFromCartRemote.sessionId,
|
||||||
|
dateTime = sessionFromCartRemote.session.dateTime,
|
||||||
|
price = sessionFromCartRemote.session.price,
|
||||||
|
availableCount = sessionFromCartRemote.session.maxCount - orders
|
||||||
|
.flatMap
|
||||||
|
{ order ->
|
||||||
|
order.sessions.filter { session ->
|
||||||
|
session.id == sessionFromCartRemote.sessionId &&
|
||||||
|
session.cinemaId == sessionFromCartRemote.session.cinemaId &&
|
||||||
|
session.dateTime == sessionFromCartRemote.session.dateTime
|
||||||
|
}
|
||||||
|
}.sumOf { session -> session.count },
|
||||||
|
count = sessionFromCartRemote.count,
|
||||||
|
cinemaId = sessionFromCartRemote.session.cinemaId,
|
||||||
|
cinema = service.getCinema(sessionFromCartRemote.session.cinemaId).toCinema()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return sessions
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUser(user: User) {
|
||||||
|
service.createUser(user.toUserRemote()).toUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateUser(user: User) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUser(user: User) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.api.user
|
||||||
|
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val login: String = "",
|
||||||
|
val password: String = "",
|
||||||
|
val role: Int = -1,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun User.toUserRemote(): UserRemote = UserRemote(
|
||||||
|
uid,
|
||||||
|
login,
|
||||||
|
password,
|
||||||
|
role = role.ordinal,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun UserRemote.toUser(): User = User(
|
||||||
|
id,
|
||||||
|
login,
|
||||||
|
password,
|
||||||
|
role = enumValues<UserRole>()[role],
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.myapplication.api.user
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserSessionRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val userId: Int = 0,
|
||||||
|
val sessionId: Int = 0,
|
||||||
|
var count: Int = 0,
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.example.myapplication.api.user
|
||||||
|
|
||||||
|
import com.example.myapplication.api.session.SessionRemote
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserSessionWithSessionRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val userId: Int = 0,
|
||||||
|
val sessionId: Int = 0,
|
||||||
|
val count: Int = 0,
|
||||||
|
val session: SessionRemote,
|
||||||
|
)
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.example.myapplication.api.usersession
|
||||||
|
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
import com.example.myapplication.api.user.UserSessionRemote
|
||||||
|
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) {
|
||||||
|
val cartSessions = service.getUserCart(userSessionCrossRef.userId)
|
||||||
|
cartSessions.forEach { session ->
|
||||||
|
if (session.sessionId == userSessionCrossRef.sessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.createUserSession(
|
||||||
|
UserSessionRemote(
|
||||||
|
id = 0,
|
||||||
|
userId = userSessionCrossRef.userId,
|
||||||
|
sessionId = userSessionCrossRef.sessionId,
|
||||||
|
count = userSessionCrossRef.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dbUserSessionRepository.insertUserSession(userSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef) {
|
||||||
|
val userSessionRemote = service.getUserSession(
|
||||||
|
userSessionCrossRef.userId,
|
||||||
|
userSessionCrossRef.sessionId
|
||||||
|
).first()
|
||||||
|
if (userSessionCrossRef.count <= 0) {
|
||||||
|
service.deleteUserSession(userSessionRemote.id)
|
||||||
|
dbUserSessionRepository.deleteUserSession(userSessionCrossRef)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userSessionRemote.count = userSessionCrossRef.count
|
||||||
|
service.updateUserCart(userSessionRemote.id, userSessionRemote)
|
||||||
|
dbUserSessionRepository.updateUserSession(userSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef) {
|
||||||
|
val userSessionRemote = service.getUserSession(
|
||||||
|
userSessionCrossRef.userId,
|
||||||
|
userSessionCrossRef.sessionId
|
||||||
|
).firstOrNull() ?: return
|
||||||
|
service.deleteUserSession(userSessionRemote.id)
|
||||||
|
dbUserSessionRepository.deleteUserSession(userSessionCrossRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUserSessions(userId: Int) {
|
||||||
|
val cart = service.getUserSessions(userId)
|
||||||
|
cart.forEach {
|
||||||
|
service.deleteUserSession(it.id)
|
||||||
|
}
|
||||||
|
dbUserSessionRepository.deleteUserSessions(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUserSessions(userSessionCrossRefs: List<UserSessionCrossRef>) {
|
||||||
|
userSessionCrossRefs.forEach { deleteUserSession(it) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.example.myapplication.LiveStore
|
||||||
|
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
|
||||||
|
import com.example.myapplication.datastore.DataStoreManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Authenticator(
|
||||||
|
dataStoreManager: DataStoreManager,
|
||||||
|
viewModel: AuthenticatorViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val login = dataStoreManager.getLogin().collectAsState(initial = "").value
|
||||||
|
|
||||||
|
LiveStore.user.value = viewModel.authUiState.user
|
||||||
|
|
||||||
|
fun synchronize() {
|
||||||
|
scope.launch {
|
||||||
|
if (login == "") {
|
||||||
|
LiveStore.user.value = null
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
viewModel.findUserByLogin(login)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronize()
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
import com.example.myapplication.database.entities.repository.UserRepository
|
||||||
|
|
||||||
|
class AuthenticatorViewModel(
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
var authUiState by mutableStateOf(AuthenticatorUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun findUserByLogin(login: String) {
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
authUiState = AuthenticatorUiState(userRepository.getUser(login))
|
||||||
|
},
|
||||||
|
actionError = {
|
||||||
|
authUiState = AuthenticatorUiState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AuthenticatorUiState(val user: User? = null)
|
258
app/src/main/java/com/example/myapplication/composeui/Cart.kt
Normal file
258
app/src/main/java/com/example/myapplication/composeui/Cart.kt
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
package com.example.myapplication.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.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.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.filled.Delete
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
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.runtime.livedata.observeAsState
|
||||||
|
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.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.LiveStore
|
||||||
|
import com.example.myapplication.R
|
||||||
|
import com.example.myapplication.api.ApiStatus
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromCart
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Cart(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val cartUiState = viewModel.cartUiState
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.refreshState()
|
||||||
|
}
|
||||||
|
when (viewModel.apiStatus) {
|
||||||
|
ApiStatus.DONE -> {
|
||||||
|
Cart(
|
||||||
|
cartUiState = cartUiState,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 10.dp),
|
||||||
|
onChangeCount = { session: SessionFromCart, count: Int ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.updateFromCart(
|
||||||
|
session = Session(
|
||||||
|
uid = session.uid,
|
||||||
|
dateTime = session.dateTime,
|
||||||
|
price = session.price,
|
||||||
|
maxCount = 0,
|
||||||
|
cinemaId = session.cinemaId
|
||||||
|
), count = count, availableCount = session.availableCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAddToOrder = { sessions: List<SessionFromCart> ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.addToOrder(sessions = sessions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDelete = { session: SessionFromCart ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.removeFromCart(
|
||||||
|
session = Session(
|
||||||
|
uid = session.uid,
|
||||||
|
dateTime = session.dateTime,
|
||||||
|
price = session.price,
|
||||||
|
maxCount = 0,
|
||||||
|
cinemaId = session.cinemaId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiStatus.LOADING -> LoadingPlaceholder()
|
||||||
|
else -> ErrorPlaceholder(
|
||||||
|
message = viewModel.apiError,
|
||||||
|
onBack = { navController.navigate(Screen.Cart.route) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Cart(
|
||||||
|
cartUiState: CartUiState,
|
||||||
|
modifier: Modifier,
|
||||||
|
onChangeCount: (SessionFromCart, Int) -> Unit,
|
||||||
|
onAddToOrder: (List<SessionFromCart>) -> Unit,
|
||||||
|
onDelete: (SessionFromCart) -> Unit
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
items(cartUiState.sessionList, key = { it.uid.toString() }) { session ->
|
||||||
|
SessionListItem(
|
||||||
|
session = session,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.secondary),
|
||||||
|
onChangeCount = onChangeCount,
|
||||||
|
onDelete = onDelete,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(48.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
if (user.value?.role == UserRole.USER) {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { onAddToOrder(cartUiState.sessionList) },
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(6.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) { Text("Купить") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SessionListItem(
|
||||||
|
session: SessionFromCart,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onChangeCount: (SessionFromCart, Int) -> Unit,
|
||||||
|
onDelete: (SessionFromCart) -> Unit
|
||||||
|
) {
|
||||||
|
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
val formattedDate = dateFormatter.format(session.dateTime)
|
||||||
|
Text(
|
||||||
|
text = formattedDate,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
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" +
|
||||||
|
if (session.availableCount <= 0) "Недоступно" else "${session.count}/${session.availableCount}",
|
||||||
|
color = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = MaterialTheme.colorScheme.primary)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onDelete(session) }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = "Удалить",
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1F))
|
||||||
|
|
||||||
|
if (session.availableCount > 0) {
|
||||||
|
IconButton(
|
||||||
|
enabled = session.count != 1,
|
||||||
|
onClick = { onChangeCount(session, session.count - 1) }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = R.drawable.minus),
|
||||||
|
contentDescription = "Уменьшить",
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "${session.count}",
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
enabled = session.count != session.availableCount,
|
||||||
|
onClick = {
|
||||||
|
onChangeCount(
|
||||||
|
session,
|
||||||
|
if (session.count != session.availableCount) session.count + 1 else session.count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = "Увеличить",
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import com.example.myapplication.LiveStore
|
||||||
|
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 org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
class CartViewModel(
|
||||||
|
private val userSessionRepository: UserSessionRepository,
|
||||||
|
private val orderRepository: OrderRepository,
|
||||||
|
private val orderSessionRepository: OrderSessionRepository,
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
) : MyViewModel() {
|
||||||
|
private var isLoading: Boolean = false
|
||||||
|
var cartUiState by mutableStateOf(CartUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun refreshState(needLoadingScreen: Boolean = true) {
|
||||||
|
val userId: Int = LiveStore.user.value?.uid ?: return
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
cartUiState = CartUiState(userRepository.getCartByUser(userId))
|
||||||
|
}, actionError = {
|
||||||
|
cartUiState = CartUiState()
|
||||||
|
},
|
||||||
|
needLoadingScreen = needLoadingScreen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun addToOrder(sessions: List<SessionFromCart>) {
|
||||||
|
if (isLoading)
|
||||||
|
return
|
||||||
|
isLoading = true
|
||||||
|
val userId: Int = LiveStore.user.value?.uid ?: return
|
||||||
|
val cart = sessions.filter { it.availableCount != 0 }
|
||||||
|
if (cart.isEmpty())
|
||||||
|
return
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
val orderId = orderRepository.insertOrder(Order(0, userId, LocalDateTime.now()))
|
||||||
|
cart.forEach { session ->
|
||||||
|
if (session.availableCount > 0) {
|
||||||
|
orderSessionRepository.insertOrderSession(
|
||||||
|
OrderSessionCrossRef(
|
||||||
|
orderId.toInt(),
|
||||||
|
session.uid,
|
||||||
|
session.price,
|
||||||
|
session.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userSessionRepository.deleteUserSessions(cart.map {
|
||||||
|
UserSessionCrossRef(userId, it.uid, it.count)
|
||||||
|
})
|
||||||
|
refreshState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeFromCart(session: Session, count: Int = 0) {
|
||||||
|
val userId: Int = LiveStore.user.value?.uid ?: return
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
userSessionRepository.deleteUserSession(
|
||||||
|
UserSessionCrossRef(
|
||||||
|
userId,
|
||||||
|
session.uid,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
refreshState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateFromCart(session: Session, count: Int, availableCount: Int) {
|
||||||
|
val userId: Int = LiveStore.user.value?.uid ?: return
|
||||||
|
if (count == 0) {
|
||||||
|
removeFromCart(session, count)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (count > availableCount)
|
||||||
|
return
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
userSessionRepository.updateUserSession(
|
||||||
|
UserSessionCrossRef(
|
||||||
|
userId,
|
||||||
|
session.uid,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
refreshState()
|
||||||
|
},
|
||||||
|
actionError = { },
|
||||||
|
needLoadingScreen = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CartUiState(val sessionList: List<SessionFromCart> = listOf())
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.example.myapplication.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 com.example.myapplication.api.ApiStatus
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
open class MyViewModel : ViewModel() {
|
||||||
|
var apiStatus by mutableStateOf(ApiStatus.DONE)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var apiError by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun runInScope(
|
||||||
|
actionSuccess: suspend () -> Unit,
|
||||||
|
actionError: suspend () -> Unit,
|
||||||
|
needLoadingScreen: Boolean = true,
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (needLoadingScreen)
|
||||||
|
apiStatus = ApiStatus.LOADING
|
||||||
|
runCatching {
|
||||||
|
actionSuccess()
|
||||||
|
apiStatus = ApiStatus.DONE
|
||||||
|
apiError = ""
|
||||||
|
}.onFailure { e: Throwable ->
|
||||||
|
when (e) {
|
||||||
|
is IOException,
|
||||||
|
is HttpException -> {
|
||||||
|
actionError()
|
||||||
|
apiStatus = ApiStatus.ERROR
|
||||||
|
apiError = e.localizedMessage ?: e.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runInScope(actionSuccess: suspend () -> Unit) {
|
||||||
|
runInScope(actionSuccess, actionError = {})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.material3.Button
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.TextUnitType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.example.myapplication.R
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ErrorPlaceholder(message: String, onBack: () -> Unit) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(10.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = TextUnit(value = 20F, type = TextUnitType.Sp),
|
||||||
|
text = message,
|
||||||
|
color = Color(0xFFFF1744)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(bottom = 10.dp))
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = { onBack() }
|
||||||
|
) {
|
||||||
|
Text(stringResource(id = R.string.back))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingPlaceholder() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(10.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = TextUnit(value = 25F, type = TextUnitType.Sp),
|
||||||
|
text = stringResource(id = R.string.loading)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
153
app/src/main/java/com/example/myapplication/composeui/Report.kt
Normal file
153
app/src/main/java/com/example/myapplication/composeui/Report.kt
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
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.Text
|
||||||
|
import androidx.compose.material3.rememberDatePickerState
|
||||||
|
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.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.api.ApiStatus
|
||||||
|
import com.example.myapplication.api.session.ReportRemote
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun Report(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val dateStateStart = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
|
||||||
|
val dateStateEnd = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
when (viewModel.apiStatus) {
|
||||||
|
ApiStatus.DONE -> {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 10.dp),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = "Начало периода",
|
||||||
|
style = MaterialTheme.typography.headlineLarge
|
||||||
|
)
|
||||||
|
DatePicker(state = dateStateStart)
|
||||||
|
val selectedDateStart = dateStateStart.selectedDateMillis
|
||||||
|
if (selectedDateStart != null) {
|
||||||
|
viewModel.onUpdate(
|
||||||
|
viewModel.reportUiState.reportDetails.copy(
|
||||||
|
startDate =
|
||||||
|
Date(selectedDateStart)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
viewModel.onUpdate(viewModel.reportUiState.reportDetails.copy(startDate = Date(0)))
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Конец периода",
|
||||||
|
style = MaterialTheme.typography.headlineLarge
|
||||||
|
)
|
||||||
|
DatePicker(state = dateStateEnd)
|
||||||
|
val selectedDateEnd = dateStateEnd.selectedDateMillis
|
||||||
|
if (selectedDateEnd != null) {
|
||||||
|
viewModel.onUpdate(
|
||||||
|
viewModel.reportUiState.reportDetails.copy(
|
||||||
|
endDate =
|
||||||
|
Date(selectedDateEnd)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
viewModel.onUpdate(viewModel.reportUiState.reportDetails.copy(endDate = Date(0)))
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = { coroutineScope.launch { viewModel.getReport() } },
|
||||||
|
enabled = viewModel.reportUiState.isEntryValid,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Получить отчет")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
CardScreen(report = viewModel.reportResultUiState.report)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiStatus.LOADING -> LoadingPlaceholder()
|
||||||
|
else -> ErrorPlaceholder(
|
||||||
|
message = viewModel.apiError,
|
||||||
|
onBack = { navController.navigate(Screen.Report.route) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CardScreen(report: ReportRemote?) {
|
||||||
|
if (report == null) return
|
||||||
|
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
report.reportData.forEach {
|
||||||
|
val (cinemaName, ticketDateTime, ticketPrice, ticketQuantity, ticketsPurchased, revenue) = it
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.border(width = 1.dp, color = MaterialTheme.colorScheme.onBackground, shape = MaterialTheme.shapes.small)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.background(color = Color.Transparent)
|
||||||
|
) {
|
||||||
|
Text(text = "Фильм: $cinemaName")
|
||||||
|
Text(text = "Сеанс: ${dateFormatter.format(ticketDateTime)}")
|
||||||
|
Text(text = "Стоимость: $ticketPrice")
|
||||||
|
Text(text = "Максимальное кол-во билетов: $ticketQuantity")
|
||||||
|
Text(text = "Купили: $ticketsPurchased")
|
||||||
|
Text(text = "Выручка: $revenue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
Text(text = "Итого: ")
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.border(width = 1.dp, color = MaterialTheme.colorScheme.onBackground, shape = MaterialTheme.shapes.small)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.background(color = Color.Transparent)
|
||||||
|
) {
|
||||||
|
Text(text = "Купили: ${report.totalPurchasedTickets}")
|
||||||
|
Text(text = "Выручка: ${report.totalRevenue}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import com.example.myapplication.api.session.ReportRemote
|
||||||
|
import com.example.myapplication.api.session.RestSessionRepository
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class ReportViewModel(private val serialRepository: RestSessionRepository) : MyViewModel() {
|
||||||
|
var reportUiState by mutableStateOf(ReportUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var reportResultUiState by mutableStateOf(ReportResultUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun onUpdate(reportDetails: ReportDetails) {
|
||||||
|
reportUiState = ReportUiState(
|
||||||
|
reportDetails = reportDetails,
|
||||||
|
isEntryValid = validateInput(reportDetails)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateInput(uiState: ReportDetails = reportUiState.reportDetails): Boolean {
|
||||||
|
return with(uiState) {
|
||||||
|
startDate != Date(0)
|
||||||
|
&& endDate != Date(0)
|
||||||
|
&& startDate <= endDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReport() {
|
||||||
|
if (validateInput()) {
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
val temp = serialRepository.getReport(
|
||||||
|
reportUiState.reportDetails.startDate,
|
||||||
|
reportUiState.reportDetails.endDate
|
||||||
|
)
|
||||||
|
reportResultUiState = ReportResultUiState(temp)
|
||||||
|
}, actionError = {
|
||||||
|
reportResultUiState = ReportResultUiState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ReportDetails(
|
||||||
|
val startDate: Date = Date(0),
|
||||||
|
val endDate: Date = Date(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReportUiState(
|
||||||
|
val reportDetails: ReportDetails = ReportDetails(),
|
||||||
|
val isEntryValid: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReportResultUiState(
|
||||||
|
val report: ReportRemote? = null
|
||||||
|
)
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.example.myapplication.composeui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Search(
|
||||||
|
initValue: String,
|
||||||
|
onDone: (String) -> Unit,
|
||||||
|
modifier: Modifier
|
||||||
|
) {
|
||||||
|
val maxLength = 20
|
||||||
|
val (value, setValue) = remember { mutableStateOf(initValue) }
|
||||||
|
fun clear() {
|
||||||
|
setValue("")
|
||||||
|
onDone("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleChange(newValue: String) {
|
||||||
|
if (newValue.length > maxLength) {
|
||||||
|
return
|
||||||
|
} else if (newValue.isEmpty()) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
setValue(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleDone() {
|
||||||
|
onDone(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { handleChange(it) },
|
||||||
|
modifier = modifier,
|
||||||
|
singleLine = true,
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = { handleDone() }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.clickable { handleDone() },
|
||||||
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
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.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
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.livedata.observeAsState
|
||||||
|
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.LiveStore
|
||||||
|
import com.example.myapplication.composeui.Cart
|
||||||
|
import com.example.myapplication.composeui.Report
|
||||||
|
import com.example.myapplication.composeui.Search
|
||||||
|
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.database.entities.model.UserRole
|
||||||
|
import com.example.myapplication.datastore.DataStoreManager
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Topbar(
|
||||||
|
navController: NavHostController,
|
||||||
|
currentScreen: Screen?
|
||||||
|
) {
|
||||||
|
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))
|
||||||
|
|
||||||
|
Search(
|
||||||
|
initValue = LiveStore.searchRequest.value ?: "",
|
||||||
|
onDone = {
|
||||||
|
navController.navigate(Screen.CinemaList.route)
|
||||||
|
LiveStore.searchRequest.value = it
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(36.dp)
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
RoundedCornerShape(18.dp)
|
||||||
|
)
|
||||||
|
.padding(start = 13.dp, top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Navbar(
|
||||||
|
navController: NavHostController,
|
||||||
|
currentDestination: NavDestination?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
NavigationBar(modifier = modifier, containerColor = MaterialTheme.colorScheme.primary) {
|
||||||
|
Screen.bottomBarItems.forEach { screen ->
|
||||||
|
if (screen.route != Screen.Report.route || user.value?.role == UserRole.ADMIN) {
|
||||||
|
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) }
|
||||||
|
composable(Screen.Cart.route) { Cart(navController) }
|
||||||
|
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(navController) }
|
||||||
|
}
|
||||||
|
composable(Screen.Report.route) { Report(navController) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,59 @@
|
|||||||
|
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
|
||||||
|
),
|
||||||
|
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
|
||||||
|
),
|
||||||
|
Report(
|
||||||
|
"Report", R.string.Report_title,
|
||||||
|
);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val bottomBarItems = listOf(
|
||||||
|
CinemaList,
|
||||||
|
Cart,
|
||||||
|
OrderList,
|
||||||
|
Report
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
userRepository,
|
||||||
|
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,195 @@
|
|||||||
|
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 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,74 @@
|
|||||||
|
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.composeui.AuthenticatorViewModel
|
||||||
|
import com.example.myapplication.composeui.CartViewModel
|
||||||
|
import com.example.myapplication.composeui.ReportViewModel
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
UserProfileViewModel(
|
||||||
|
cinemaApplication().container.userRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
AuthenticatorViewModel(cinemaApplication().container.userRestRepository)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
ReportViewModel(cinemaApplication().container.sessionRestRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CreationExtras.cinemaApplication(): CinemaApplication =
|
||||||
|
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as CinemaApplication)
|
@ -0,0 +1,201 @@
|
|||||||
|
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.livedata.observeAsState
|
||||||
|
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.LiveStore
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CinemaList(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: CinemaListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val cinemaPagingItems = viewModel.cinemaPagingFlow.flow.collectAsLazyPagingItems()
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
val searchPattern = LiveStore.searchRequest.observeAsState("")
|
||||||
|
|
||||||
|
LaunchedEffect(searchPattern.value) {
|
||||||
|
viewModel.refresh()
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
topBar = {},
|
||||||
|
floatingActionButton = {
|
||||||
|
if (user.value?.role == UserRole.ADMIN) {
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
if (user.value?.role == UserRole.ADMIN) {
|
||||||
|
// Добавляем пустое пространство для разделения текста и кнопки
|
||||||
|
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,39 @@
|
|||||||
|
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.viewModelScope
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.cachedIn
|
||||||
|
import com.example.myapplication.LiveStore
|
||||||
|
import com.example.myapplication.composeui.MyViewModel
|
||||||
|
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
|
||||||
|
) : MyViewModel() {
|
||||||
|
var cinemaPagingFlow by mutableStateOf(CinemaPagingFlowState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
val name = "%${LiveStore.searchRequest.value}%"
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
val pagingSource = cinemaRepository.getAllCinemas(name)
|
||||||
|
cinemaPagingFlow = CinemaPagingFlowState(pagingSource.cachedIn(viewModelScope))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteCinema(cinema: Cinema) {
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
cinemaRepository.deleteCinema(cinema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CinemaPagingFlowState(
|
||||||
|
val flow: Flow<PagingData<Cinema>> = emptyFlow(),
|
||||||
|
)
|
@ -0,0 +1,156 @@
|
|||||||
|
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.runtime.livedata.observeAsState
|
||||||
|
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.LiveStore
|
||||||
|
import com.example.myapplication.api.ApiStatus
|
||||||
|
import com.example.myapplication.composeui.ErrorPlaceholder
|
||||||
|
import com.example.myapplication.composeui.LoadingPlaceholder
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.database.entities.model.Cinema
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CinemaView(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: CinemaViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
) {
|
||||||
|
val cinemaUiState = viewModel.cinemaUiState
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.refreshState()
|
||||||
|
}
|
||||||
|
when (viewModel.apiStatus) {
|
||||||
|
ApiStatus.DONE -> {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
if (user.value?.role == UserRole.ADMIN) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiStatus.LOADING -> LoadingPlaceholder()
|
||||||
|
else -> ErrorPlaceholder(
|
||||||
|
message = viewModel.apiError,
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
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 com.example.myapplication.LiveStore
|
||||||
|
import com.example.myapplication.composeui.MyViewModel
|
||||||
|
import com.example.myapplication.database.entities.model.CinemaWithSessions
|
||||||
|
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.CinemaRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.SessionRepository
|
||||||
|
import com.example.myapplication.database.entities.repository.UserSessionRepository
|
||||||
|
|
||||||
|
class CinemaViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle, private val cinemaRepository: CinemaRepository,
|
||||||
|
private val sessionRepository: SessionRepository,
|
||||||
|
private val userSessionRepository: UserSessionRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
|
||||||
|
var cinemaUiState by mutableStateOf(CinemaUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun refreshState() {
|
||||||
|
if (cinemaUid > 0) {
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
|
||||||
|
}, actionError = {
|
||||||
|
cinemaUiState = CinemaUiState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteSession(session: SessionFromCinema) {
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
sessionRepository.deleteSession(
|
||||||
|
Session(
|
||||||
|
uid = session.uid,
|
||||||
|
dateTime = session.dateTime,
|
||||||
|
price = session.price,
|
||||||
|
maxCount = 0,
|
||||||
|
cinemaId = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
refreshState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addSessionInCart(sessionId: Int, count: Int = 1) {
|
||||||
|
val userId: Int = LiveStore.user.value?.uid ?: return
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
userSessionRepository.insertUserSession(
|
||||||
|
UserSessionCrossRef(
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}, actionError = {}, needLoadingScreen = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CinemaUiState(val cinemaWithSessions: CinemaWithSessions? = null)
|
@ -0,0 +1,99 @@
|
|||||||
|
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.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.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.LiveStore
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import com.example.myapplication.ui.theme.PmudemoTheme
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderList(
|
||||||
|
navController: NavController?,
|
||||||
|
viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
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 dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
val orderId = Screen.OrderView.route.replace("{id}", order!!.uid.toString())
|
||||||
|
val formattedDate = dateFormatter.format(order.dateTime)
|
||||||
|
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)
|
||||||
|
) {
|
||||||
|
if (LiveStore.user.value?.role == UserRole.ADMIN)
|
||||||
|
Text(
|
||||||
|
"Заказ №${order.uid}, ${formattedDate}\n" +
|
||||||
|
"Пользователь: ${order.user?.login ?: "Неизвестно"}",
|
||||||
|
color = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Text(
|
||||||
|
"Заказ №${order.uid}, $formattedDate",
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.myapplication.database.entities.composeui
|
||||||
|
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import com.example.myapplication.composeui.MyViewModel
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import com.example.myapplication.database.entities.model.OrderWithUser
|
||||||
|
import com.example.myapplication.database.entities.repository.OrderRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class OrderListViewModel(
|
||||||
|
orderRepository: OrderRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
val orderListUiState: Flow<PagingData<OrderWithUser>> = orderRepository.getAllOrders()
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
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.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.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.api.ApiStatus
|
||||||
|
import com.example.myapplication.composeui.ErrorPlaceholder
|
||||||
|
import com.example.myapplication.composeui.LoadingPlaceholder
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderView(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val orderUiState = viewModel.orderUiState
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.refreshState()
|
||||||
|
}
|
||||||
|
when (viewModel.apiStatus) {
|
||||||
|
ApiStatus.DONE -> {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
) {
|
||||||
|
items(orderUiState.sessionList) { session ->
|
||||||
|
val count = remember { mutableIntStateOf(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.intValue}",
|
||||||
|
color = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiStatus.LOADING -> LoadingPlaceholder()
|
||||||
|
else -> ErrorPlaceholder(
|
||||||
|
message = viewModel.apiError,
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
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 com.example.myapplication.composeui.MyViewModel
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
import com.example.myapplication.database.entities.repository.OrderRepository
|
||||||
|
|
||||||
|
class OrderViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val orderRepository: OrderRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
private val orderUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
|
||||||
|
var orderUiState by mutableStateOf(OrderUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun refreshState() {
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
orderUiState = OrderUiState(orderRepository.getOrder(orderUid))
|
||||||
|
}, actionError = {
|
||||||
|
orderUiState = OrderUiState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class OrderUiState(val sessionList: List<SessionFromOrder> = listOf())
|
@ -0,0 +1,148 @@
|
|||||||
|
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.livedata.observeAsState
|
||||||
|
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.navigation.NavController
|
||||||
|
import com.example.myapplication.LiveStore
|
||||||
|
import com.example.myapplication.R
|
||||||
|
import com.example.myapplication.composeui.navigation.Screen
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SessionList(
|
||||||
|
cinemaWithSessionsViewModel: CinemaViewModel,
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val cinemaWithSessions = cinemaWithSessionsViewModel.cinemaUiState.cinemaWithSessions!!
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if (user.value?.role == UserRole.ADMIN)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (user.value?.role == UserRole.USER) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
cinemaWithSessionsViewModel.addSessionInCart(sessionId = session.uid)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ShoppingCart,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (user.value?.role == UserRole.ADMIN) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
cinemaWithSessionsViewModel.deleteSession(session = session)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
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.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.example.myapplication.LiveStore
|
||||||
|
import com.example.myapplication.datastore.DataStoreManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserProfile(
|
||||||
|
isDarkTheme: MutableState<Boolean>,
|
||||||
|
dataStoreManager: DataStoreManager,
|
||||||
|
viewModel: UserProfileViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
) {
|
||||||
|
var isRegistration by remember { mutableStateOf(false) }
|
||||||
|
val coroutine = rememberCoroutineScope()
|
||||||
|
val errorStringId: Int? = viewModel.userUiState.errorId
|
||||||
|
val errorMessage =
|
||||||
|
if (errorStringId == null || errorStringId == 0) "" else stringResource(errorStringId)
|
||||||
|
val user = LiveStore.user.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(errorStringId) {
|
||||||
|
if (errorStringId == 0) {
|
||||||
|
isRegistration = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
item {
|
||||||
|
if (user.value != null) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Текущий пользователь: " + (user.value?.login ?: ""),
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
enabled = user.value != null,
|
||||||
|
onClick = {
|
||||||
|
coroutine.launch {
|
||||||
|
dataStoreManager.setLogin("")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 2.dp, top = 10.dp)
|
||||||
|
) { Text("Выход") }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = errorMessage,
|
||||||
|
color = Color.Red
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Логин",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
BasicTextField(
|
||||||
|
value = viewModel.userUiState.details.login,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateUiState(viewModel.userUiState.details.copy(login = it))
|
||||||
|
},
|
||||||
|
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 = viewModel.userUiState.details.password,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateUiState(viewModel.userUiState.details.copy(password = it))
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size(36.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.secondary,
|
||||||
|
RoundedCornerShape(18.dp)
|
||||||
|
)
|
||||||
|
.padding(start = 13.dp, top = 8.dp),
|
||||||
|
visualTransformation = PasswordVisualTransformation()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isRegistration) {
|
||||||
|
Text(
|
||||||
|
text = "Подтверждение пароля",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
BasicTextField(
|
||||||
|
value = viewModel.userUiState.details.passwordConfirm,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateUiState(
|
||||||
|
viewModel.userUiState.details.copy(
|
||||||
|
passwordConfirm = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
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 = { coroutine.launch { viewModel.signUp() } },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 2.dp, top = 8.dp)
|
||||||
|
) {
|
||||||
|
Text("Регистрация")
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Уже есть аккаунт? Войти",
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
isRegistration = false
|
||||||
|
}
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
coroutine.launch {
|
||||||
|
viewModel.signIn(dataStoreManager)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 2.dp, top = 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = isDarkTheme.value,
|
||||||
|
onCheckedChange = {
|
||||||
|
isDarkTheme.value = !isDarkTheme.value
|
||||||
|
coroutine.launch {
|
||||||
|
if (isDarkTheme.value) {
|
||||||
|
dataStoreManager.setDarkTheme("Dark")
|
||||||
|
} else {
|
||||||
|
dataStoreManager.setDarkTheme("Light")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = switchColors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.example.myapplication.database.entities.composeui
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import com.example.myapplication.MainComposeActivity
|
||||||
|
import com.example.myapplication.R
|
||||||
|
import com.example.myapplication.composeui.MyViewModel
|
||||||
|
import com.example.myapplication.database.entities.model.User
|
||||||
|
import com.example.myapplication.database.entities.repository.UserRepository
|
||||||
|
import com.example.myapplication.datastore.DataStoreManager
|
||||||
|
|
||||||
|
class UserProfileViewModel(
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
var userUiState by mutableStateOf(UserUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun updateUiState(userDetails: UserDetails) {
|
||||||
|
userUiState = UserUiState(
|
||||||
|
details = userDetails,
|
||||||
|
errorId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun signIn(dataStoreManager: DataStoreManager) {
|
||||||
|
userUiState.details.passwordConfirm = userUiState.details.password
|
||||||
|
var errorId: Int? = validateInput(userUiState.details)
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
if (errorId == null) {
|
||||||
|
val overlap: User? = userRepository.getUser(userUiState.details.login)
|
||||||
|
if (overlap == null || userUiState.details.password != overlap.password) {
|
||||||
|
errorId = R.string.err_04
|
||||||
|
} else {
|
||||||
|
dataStoreManager.setLogin("")
|
||||||
|
dataStoreManager.setLogin(userUiState.details.login)
|
||||||
|
Log.d("UserProfileViewModel", "sign in success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userUiState = UserUiState(
|
||||||
|
details = userUiState.details,
|
||||||
|
errorId = errorId
|
||||||
|
)
|
||||||
|
}, actionError = {
|
||||||
|
errorId = R.string.err_06
|
||||||
|
userUiState = UserUiState(
|
||||||
|
details = userUiState.details,
|
||||||
|
errorId = errorId
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun signUp() {
|
||||||
|
var errorId: Int? = validateInput(userUiState.details)
|
||||||
|
runInScope(actionSuccess = {
|
||||||
|
if (errorId == null) {
|
||||||
|
val overlap = userRepository.getUser(userUiState.details.login)
|
||||||
|
if (overlap != null) {
|
||||||
|
errorId = R.string.err_03
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorId == null) {
|
||||||
|
userRepository.insertUser(userUiState.details.toUser())
|
||||||
|
val toast = Toast.makeText(
|
||||||
|
MainComposeActivity.appContext,
|
||||||
|
"Вы зарегистрированы",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
toast.show()
|
||||||
|
errorId = 0
|
||||||
|
}
|
||||||
|
userUiState = UserUiState(
|
||||||
|
details = userUiState.details,
|
||||||
|
errorId = errorId
|
||||||
|
)
|
||||||
|
}, actionError = {
|
||||||
|
errorId = R.string.err_06
|
||||||
|
userUiState = UserUiState(
|
||||||
|
details = userUiState.details,
|
||||||
|
errorId = errorId
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateInput(details: UserDetails = userUiState.details): Int? {
|
||||||
|
return if (details.login.isBlank()) {
|
||||||
|
R.string.err_01
|
||||||
|
} else if (details.password.isBlank()) {
|
||||||
|
R.string.err_02
|
||||||
|
} else if (details.passwordConfirm.isBlank()
|
||||||
|
|| details.password != details.passwordConfirm
|
||||||
|
) {
|
||||||
|
R.string.err_05
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UserDetails(
|
||||||
|
val login: String = "",
|
||||||
|
val password: String = "",
|
||||||
|
var passwordConfirm: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserUiState(
|
||||||
|
val details: UserDetails = UserDetails(),
|
||||||
|
val errorId: Int? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
fun UserDetails.toUser(uid: Int = 0): User = User(
|
||||||
|
uid = uid,
|
||||||
|
login = login,
|
||||||
|
password = password,
|
||||||
|
)
|
@ -0,0 +1,137 @@
|
|||||||
|
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.api.ApiStatus
|
||||||
|
import com.example.myapplication.composeui.ErrorPlaceholder
|
||||||
|
import com.example.myapplication.composeui.LoadingPlaceholder
|
||||||
|
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()
|
||||||
|
when (viewModel.apiStatus) {
|
||||||
|
ApiStatus.DONE -> {
|
||||||
|
CinemaEdit(
|
||||||
|
cinemaUiState = viewModel.cinemaUiState,
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.saveCinema()
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUpdate = viewModel::updateUiState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ApiStatus.LOADING -> LoadingPlaceholder()
|
||||||
|
else -> ErrorPlaceholder(
|
||||||
|
message = viewModel.apiError,
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
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,104 @@
|
|||||||
|
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 com.example.myapplication.composeui.MyViewModel
|
||||||
|
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
|
||||||
|
|
||||||
|
class CinemaEditViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val cinemaRepository: CinemaRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
var cinemaUiState by mutableStateOf(CinemaUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (cinemaUid > 0) {
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
cinemaUiState = cinemaRepository.getCinema(cinemaUid)
|
||||||
|
.toUiState(true)
|
||||||
|
},
|
||||||
|
actionError = {
|
||||||
|
cinemaUiState = CinemaUiState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateUiState(cinemaDetails: CinemaDetails) {
|
||||||
|
cinemaUiState = CinemaUiState(
|
||||||
|
cinemaDetails = cinemaDetails,
|
||||||
|
isEntryValid = validateInput(cinemaDetails)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveCinema() {
|
||||||
|
if (validateInput()) {
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
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,161 @@
|
|||||||
|
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.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.api.ApiStatus
|
||||||
|
import com.example.myapplication.composeui.ErrorPlaceholder
|
||||||
|
import com.example.myapplication.composeui.LoadingPlaceholder
|
||||||
|
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()
|
||||||
|
when (viewModel.apiStatus) {
|
||||||
|
ApiStatus.DONE -> {
|
||||||
|
SessionEdit(
|
||||||
|
sessionUiState = viewModel.sessionUiState,
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.saveSession()
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUpdate = viewModel::updateUiState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiStatus.LOADING -> LoadingPlaceholder()
|
||||||
|
else -> ErrorPlaceholder(
|
||||||
|
message = viewModel.apiError,
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.Save_button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
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 com.example.myapplication.composeui.MyViewModel
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
import com.example.myapplication.database.entities.repository.SessionRepository
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
class SessionEditViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val sessionRepository: SessionRepository
|
||||||
|
) : MyViewModel() {
|
||||||
|
var sessionUiState by mutableStateOf(SessionUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val sessionUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
private val cinemaUid: Int = checkNotNull(savedStateHandle["cinemaId"])
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (sessionUid > 0) {
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
sessionUiState = sessionRepository.getSession(sessionUid)
|
||||||
|
.toUiState(true)
|
||||||
|
},
|
||||||
|
actionError = {
|
||||||
|
sessionUiState = SessionUiState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateUiState(sessionDetails: SessionDetails) {
|
||||||
|
sessionUiState = SessionUiState(
|
||||||
|
sessionDetails = sessionDetails,
|
||||||
|
isEntryValid = validateInput(sessionDetails)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveSession() {
|
||||||
|
if (validateInput()) {
|
||||||
|
runInScope(
|
||||||
|
actionSuccess = {
|
||||||
|
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,43 @@
|
|||||||
|
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.OnConflictStrategy
|
||||||
|
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 * from cinemas where cinemas.name like :name order by name collate nocase asc")
|
||||||
|
fun getAll(name: String): 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(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
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,43 @@
|
|||||||
|
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.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.entities.model.Order
|
||||||
|
import com.example.myapplication.database.entities.model.OrderWithUser
|
||||||
|
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, OrderWithUser>
|
||||||
|
|
||||||
|
@Query("select * from orders " +
|
||||||
|
"left join users on orders.user_id = users.uid")
|
||||||
|
fun getAll(): PagingSource<Int, OrderWithUser>
|
||||||
|
|
||||||
|
@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(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
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,36 @@
|
|||||||
|
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.Session
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface SessionDao {
|
||||||
|
@Query("select * from sessions where sessions.uid = :uid")
|
||||||
|
suspend fun getByUid(uid: Int): Session
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
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,42 @@
|
|||||||
|
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.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 * from users where users.login=:login LIMIT 1")
|
||||||
|
suspend fun getByLogin(login: String): 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(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
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(vararg 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,31 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "orders"
|
||||||
|
)
|
||||||
|
data class Order(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val uid: Int,
|
||||||
|
@ColumnInfo(name = "user_id", index = true)
|
||||||
|
val userId: Int = 0,
|
||||||
|
@ColumnInfo(name = "date_time")
|
||||||
|
val dateTime: LocalDateTime,
|
||||||
|
) {
|
||||||
|
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,18 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Relation
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
|
data class OrderWithUser(
|
||||||
|
val uid: Int,
|
||||||
|
@ColumnInfo(name = "user_id", index = true)
|
||||||
|
val userId: Int = 0,
|
||||||
|
@ColumnInfo(name = "date_time")
|
||||||
|
val dateTime: LocalDateTime,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "user_id",
|
||||||
|
entity = User::class,
|
||||||
|
entityColumn = "uid"
|
||||||
|
) val user: User?
|
||||||
|
)
|
@ -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,25 @@
|
|||||||
|
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,
|
||||||
|
val role: UserRole = UserRole.USER
|
||||||
|
) {
|
||||||
|
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,6 @@
|
|||||||
|
package com.example.myapplication.database.entities.model
|
||||||
|
|
||||||
|
enum class UserRole(val value: Int) {
|
||||||
|
USER(0),
|
||||||
|
ADMIN(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,14 @@
|
|||||||
|
package com.example.myapplication.database.entities.repository
|
||||||
|
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
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(name: String): 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,53 @@
|
|||||||
|
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(name: String): Flow<PagingData<Cinema>> = Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
pagingSourceFactory = { cinemaDao.getAll(name) }
|
||||||
|
).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()
|
||||||
|
|
||||||
|
fun getAllCinemasPagingSource(name: String): PagingSource<Int, Cinema> = cinemaDao.getAll(name)
|
||||||
|
|
||||||
|
suspend fun insertCinemas(cinemas: List<Cinema>) =
|
||||||
|
cinemaDao.insert(*cinemas.toTypedArray())
|
||||||
|
|
||||||
|
suspend fun clearCinemas() = cinemaDao.deleteAll()
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
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.LiveStore
|
||||||
|
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.OrderWithUser
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
import com.example.myapplication.database.entities.model.UserRole
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class OfflineOrderRepository(private val orderDao: OrderDao) : OrderRepository {
|
||||||
|
override fun getAllOrders(): Flow<PagingData<OrderWithUser>> = Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = AppContainer.LIMIT,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
pagingSourceFactory = { getAllOrdersPagingSource() }
|
||||||
|
).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(): PagingSource<Int, OrderWithUser> {
|
||||||
|
val user = LiveStore.user.value
|
||||||
|
if (user?.role == UserRole.ADMIN)
|
||||||
|
return orderDao.getAll()
|
||||||
|
return orderDao.getAll(user?.uid ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,26 @@
|
|||||||
|
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 getUser(login: String): User? = userDao.getByLogin(login)
|
||||||
|
|
||||||
|
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,23 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
override suspend fun deleteUserSessions(userSessionCrossRefs: List<UserSessionCrossRef>) =
|
||||||
|
userSessionDao.delete(*userSessionCrossRefs.toTypedArray())
|
||||||
|
|
||||||
|
suspend fun deleteSessionsByUid(sessionId: Int) = userSessionDao.deleteBySessionUid(sessionId)
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
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.OrderWithUser
|
||||||
|
import com.example.myapplication.database.entities.model.SessionFromOrder
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface OrderRepository {
|
||||||
|
fun getAllOrders(): Flow<PagingData<OrderWithUser>>
|
||||||
|
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,10 @@
|
|||||||
|
package com.example.myapplication.database.entities.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.entities.model.Session
|
||||||
|
|
||||||
|
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,14 @@
|
|||||||
|
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 getUser(login: String): 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,11 @@
|
|||||||
|
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)
|
||||||
|
suspend fun deleteUserSessions(userSessionCrossRefs: List<UserSessionCrossRef>)
|
||||||
|
}
|
@ -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,28 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
enum class RemoteKeyType(private val type: String) {
|
||||||
|
CINEMA(Cinema::class.simpleName ?: "Cinema"),
|
||||||
|
ORDER(Order::class.simpleName ?: "Order");
|
||||||
|
|
||||||
|
@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,47 @@
|
|||||||
|
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.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class DataStoreManager(private val context: Context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("Store")
|
||||||
|
val DARK_THEME = stringPreferencesKey("dark_theme")
|
||||||
|
val LOGIN = stringPreferencesKey("login")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDarkTheme(): Flow<String> {
|
||||||
|
return context.dataStore.data
|
||||||
|
.map { preferences ->
|
||||||
|
preferences[DARK_THEME] ?: "Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLogin(): Flow<String> {
|
||||||
|
return context.dataStore.data
|
||||||
|
.map { preferences ->
|
||||||
|
preferences[LOGIN] ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun saveStringValue(key: Preferences.Key<String>, value: String) {
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
preferences[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setDarkTheme(darkTheme: String) {
|
||||||
|
saveStringValue(DARK_THEME, darkTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setLogin(login: String) {
|
||||||
|
saveStringValue(LOGIN, login)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user