This commit is contained in:
dasha 2023-12-05 15:31:10 +04:00
parent e4bc55da4e
commit f499c56d14
68 changed files with 143060 additions and 202 deletions

View File

@ -2,6 +2,7 @@ plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
@ -57,11 +58,6 @@ dependencies {
implementation("io.github.vanpra.compose-material-dialogs:datetime:0.8.1-rc")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
// Pagination
val paging_version = "3.2.0-rc01"
implementation("androidx.paging:paging-runtime:$paging_version")
implementation("androidx.paging:paging-compose:$paging_version")
// Core
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
@ -74,6 +70,7 @@ dependencies {
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.compose.material:material:1.4.3")
// Room
val room_version = "2.5.2"
@ -83,6 +80,14 @@ dependencies {
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")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".CinemaApplication"
android:allowBackup="true"
@ -12,7 +14,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Pmudemo"
tools:targetApi="31">
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainComposeActivity"
android:exported="true"

View File

@ -13,15 +13,12 @@ import com.example.myapplication.composeui.navigation.MainNavbar
import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.ui.theme.PmudemoTheme
//import com.jakewharton.threetenabp.AndroidThreeTen
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")
//AndroidThreeTen.init(this)
setContent {
PmudemoTheme(darkTheme = isDarkTheme.value) {
LaunchedEffect(key1 = true) {

View File

@ -0,0 +1,26 @@
package com.example.myapplication.api
import androidx.room.TypeConverters
import com.example.myapplication.database.entities.model.LocalDateTimeConverter
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import org.threeten.bp.LocalDateTime
import org.threeten.bp.DateTimeUtils.toLocalDateTime
import org.threeten.bp.format.DateTimeFormatter
@Serializer(forClass = LocalDateTime::class)
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDateTime) {
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
encoder.encodeString(dateFormatter.format(value).toString())
}
override fun deserialize(decoder: Decoder): LocalDateTime {
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
return LocalDateTime.parse(decoder.decodeString(), dateFormatter)
}
}

View File

@ -0,0 +1,141 @@
package com.example.myapplication.api
import com.example.myapplication.api.cinema.CinemaRemote
import com.example.myapplication.api.order.OrderRemote
import com.example.myapplication.api.session.SessionFromCinemaRemote
import com.example.myapplication.api.session.SessionRemote
import com.example.myapplication.api.session.SessionWithCinemaRemote
import com.example.myapplication.api.user.UserRemote
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
interface MyServerService {
@GET("orders")
suspend fun getOrders(): List<OrderRemote>
@GET("users")
suspend fun getUsers(): List<UserRemote>
@GET("cinemas")
suspend fun getCinemas(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<CinemaRemote>
@GET("cinemas/{id}")
suspend fun getCinema(
@Path("id") id: Int,
): CinemaRemote
@POST("cinemas")
suspend fun createCinema(
@Body cinema: CinemaRemote,
): CinemaRemote
@PUT("cinemas/{id}")
suspend fun updateCinema(
@Path("id") id: Int,
@Body cinema: CinemaRemote,
): CinemaRemote
@DELETE("cinemas/{id}")
suspend fun deleteCinema(
@Path("id") id: Int,
)
@GET("cinemas/{cinemaId}/sessions")
suspend fun getSessionsForCinema(
@Path("cinemaId") cinemaId: Int
): List<SessionFromCinemaRemote>
@GET("sessions/{id}?_expand=cinema")
suspend fun getSession(
@Path("id") id: Int,
): SessionWithCinemaRemote
@POST("sessions")
suspend fun createSession(
@Body session: SessionRemote,
): SessionRemote
@PUT("sessions/{id}")
suspend fun updateSession(
@Path("id") id: Int,
@Body session: SessionRemote,
): SessionRemote
@DELETE("sessions/{id}")
suspend fun deleteSession(
@Path("id") id: Int,
): SessionFromCinemaRemote
@GET("users/{id}")
suspend fun getUserCart(
@Path("id") id: Int,
): UserRemote
@PUT("users/{id}")
suspend fun updateUserCart(
@Path("id") id: Int,
@Body userRemote: UserRemote,
): UserRemote
@GET("orders")
suspend fun getOrders(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<OrderRemote>
@GET("orders/{id}")
suspend fun getOrder(
@Path("id") id: Int,
): OrderRemote
@POST("orders")
suspend fun createOrder(
@Body cinema: OrderRemote,
): OrderRemote
@PUT("orders/{id}")
suspend fun updateOrder(
@Path("id") id: Int,
@Body orderRemote: OrderRemote,
): OrderRemote
companion object {
private const val BASE_URL = "http://192.168.154.166:8079/"
@Volatile
private var INSTANCE: MyServerService? = null
fun getInstance(): MyServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder().addInterceptor(logger).build()
val json = Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
contextual(LocalDateTimeSerializer)
}
} // Создаем экземпляр Json с ignoreUnknownKeys = true
return Retrofit.Builder().baseUrl(BASE_URL).client(client)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // Применяем конфигурацию Json
.build().create(MyServerService::class.java).also { INSTANCE = it }
}
}
}
}

View File

@ -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
)

View File

@ -0,0 +1,121 @@
package com.example.myapplication.api.cinema
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.session.toSession
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.repository.OfflineCinemaRepository
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
import com.example.myapplication.database.remotekeys.model.RemoteKeys
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class CinemaRemoteMediator(
private val service: MyServerService,
private val dbCinemaRepository: OfflineCinemaRepository,
private val dbSessionRepository: OfflineSessionRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Cinema>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Cinema>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val cinemas = service.getCinemas(page, state.config.pageSize).map { it.toCinema() }
val cinemasWithSessions = cinemas.map { cinema ->
service.getSessionsForCinema(cinema.uid).map {
service.getSession(it.id).toSession()
}
}
val endOfPaginationReached = cinemas.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CINEMA)
dbSessionRepository.clearSessions()
dbCinemaRepository.clearCinemas()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = cinemas.map {
RemoteKeys(
entityId = it.uid,
type = RemoteKeyType.CINEMA,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbCinemaRepository.insertCinemas(cinemas)
cinemasWithSessions.forEach {
try {
dbSessionRepository.insertSessions(it)
} catch (_: Exception) {
}
}
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Cinema>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { cinema ->
dbRemoteKeyRepository.getAllRemoteKeys(cinema.uid, RemoteKeyType.CINEMA)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Cinema>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { cinema ->
dbRemoteKeyRepository.getAllRemoteKeys(cinema.uid, RemoteKeyType.CINEMA)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Cinema>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { cinemaUid ->
dbRemoteKeyRepository.getAllRemoteKeys(cinemaUid, RemoteKeyType.CINEMA)
}
}
}
}

View File

@ -0,0 +1,88 @@
package com.example.myapplication.api.cinema
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.CinemaWithSessions
import com.example.myapplication.database.entities.model.SessionFromCinema
import com.example.myapplication.database.entities.repository.CinemaRepository
import com.example.myapplication.database.entities.repository.OfflineCinemaRepository
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestCinemaRepository(
private val service: MyServerService,
private val dbCinemaRepository: OfflineCinemaRepository,
private val dbSessionRepository: OfflineSessionRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : CinemaRepository {
override fun getAllCinemas(): Flow<PagingData<Cinema>> {
Log.d(RestCinemaRepository::class.simpleName, "Get cinemas")
val pagingSourceFactory = { dbCinemaRepository.getAllCinemasPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = CinemaRemoteMediator(
service,
dbCinemaRepository,
dbSessionRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getCinema(uid: Int): CinemaWithSessions {
val cinema = service.getCinema(uid).toCinema()
val sessions = service.getSessionsForCinema(uid).map { x ->
SessionFromCinema(
x.id,
x.dateTime,
x.price,
x.maxCount - service.getOrders().flatMap { order ->
order.sessions.filter { session -> session.id == x.id }
}.sumOf { session -> session.count },
uid
)
}
return CinemaWithSessions(cinema, sessions)
}
override suspend fun insertCinema(cinema: Cinema) {
service.createCinema(cinema.toCinemaRemote()).toCinema()
}
override suspend fun updateCinema(cinema: Cinema) {
service.updateCinema(cinema.uid, cinema.toCinemaRemote()).toCinema()
}
override suspend fun deleteCinema(cinema: Cinema) {
val cart = service.getUsers()
cart.forEach { userRemote ->
userRemote.sessions = userRemote.sessions.filter { x -> x.cinemaId != cinema.uid }
service.updateUserCart(userRemote.id, userRemote)
}
val orders = service.getOrders()
orders.forEach { orderRemote ->
orderRemote.sessions = orderRemote.sessions.filter { x -> x.cinemaId != cinema.uid }
service.updateOrder(orderRemote.id, orderRemote)
}
service.deleteCinema(cinema.uid)
dbCinemaRepository.deleteCinema(cinema)
}
}

View File

@ -0,0 +1,18 @@
package com.example.myapplication.api.order
import com.example.myapplication.api.session.SessionFromOrderRemote
import com.example.myapplication.database.entities.model.Order
import kotlinx.serialization.Serializable
@Serializable
data class OrderRemote(
val id: Int = 0, val userId: Int = 0, var sessions: List<SessionFromOrderRemote> = emptyList()
)
fun OrderRemote.toOrder(): Order = Order(
id, userId
)
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
uid, userId!!, sessions = emptyList()
)

View File

@ -0,0 +1,106 @@
package com.example.myapplication.api.order
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.repository.OfflineOrderRepository
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
import com.example.myapplication.database.remotekeys.model.RemoteKeys
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class OrderRemoteMediator(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Order>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Order>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val orders = service.getOrders(page, state.config.pageSize).map { it.toOrder() }
val endOfPaginationReached = orders.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
dbOrderRepository.clearOrders()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = orders.map {
RemoteKeys(
entityId = it.uid,
type = RemoteKeyType.ORDER,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbOrderRepository.insertOrders(orders)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Order>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { order ->
dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Order>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { order ->
dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Order>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { orderUid ->
dbRemoteKeyRepository.getAllRemoteKeys(orderUid, RemoteKeyType.ORDER)
}
}
}
}

View File

@ -0,0 +1,72 @@
package com.example.myapplication.api.order
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.cinema.toCinemaRemote
import com.example.myapplication.api.session.toSessionFromOrder
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
import com.example.myapplication.database.entities.model.SessionFromOrder
import com.example.myapplication.database.entities.repository.OfflineCinemaRepository
import com.example.myapplication.database.entities.repository.OfflineOrderRepository
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
import com.example.myapplication.database.entities.repository.OrderRepository
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestOrderRepository(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbCinemaRepository: OfflineCinemaRepository,
private val dbOrderSessionRepository: OfflineOrderSessionRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : OrderRepository {
override fun getAllOrders(userId: Int?): Flow<PagingData<Order>> {
Log.d(RestOrderRepository::class.simpleName, "Get orders")
val pagingSourceFactory = { dbOrderRepository.getAllOrdersPagingSource(userId) }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = OrderRemoteMediator(
service,
dbOrderRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getOrder(uid: Int): List<SessionFromOrder> {
val order = service.getOrder(uid)
dbOrderSessionRepository.deleteOrderSessions(uid)
order.sessions.map {
dbOrderSessionRepository.insertOrderSession(
OrderSessionCrossRef(
uid,
it.id,
it.frozenPrice,
it.count
)
)
}
return order.sessions.map { x -> x.toSessionFromOrder(dbCinemaRepository.getCinema(x.cinemaId).cinema.toCinemaRemote()) }
}
override suspend fun insertOrder(order: Order): Long {
return dbOrderRepository.insertOrder(service.createOrder(order.toOrderRemote()).toOrder())
}
}

View File

@ -0,0 +1,39 @@
package com.example.myapplication.api.ordersession
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.session.SessionFromOrderRemote
import com.example.myapplication.api.session.toSession
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
import com.example.myapplication.database.entities.repository.OrderSessionRepository
class RestOrderSessionRepository(
private val service: MyServerService,
private val dbOrderSessionRepository: OfflineOrderSessionRepository
) : OrderSessionRepository {
override suspend fun insertOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
var orderRemote = service.getOrder(orderSessionCrossRef.orderId)
val session = service.getSession(orderSessionCrossRef.sessionId).toSession()
val sessionFromOrder = SessionFromOrderRemote(
session.uid,
session.dateTime,
session.price,
orderSessionCrossRef.count,
session.cinemaId
)
val updatedSessions = orderRemote.sessions.toMutableList()
updatedSessions.add(sessionFromOrder)
orderRemote = orderRemote.copy(sessions = updatedSessions)
service.updateOrder(orderSessionCrossRef.orderId, orderRemote)
dbOrderSessionRepository.insertOrderSession(orderSessionCrossRef)
}
override suspend fun updateOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
}
override suspend fun deleteOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
}
}

View File

@ -0,0 +1,71 @@
package com.example.myapplication.api.session
import com.example.myapplication.api.cinema.CinemaRemote
import com.example.myapplication.api.cinema.toCinema
import com.example.myapplication.api.session.SessionFromCinemaRemote
import com.example.myapplication.api.session.toSessionFromCinema
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.CinemaWithSessions
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/*
@Serializable
data class CinemaWithSessionsRemote(
val id: Int = 0,
val name: String = "",
val description: String = "",
val image: ByteArray? = null,
val year: Long = 0,
@SerialName("sessions")
val sessions: List<SessionFromCinemaRemote>,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CinemaWithSessionsRemote
if (id != other.id) return false
if (name != other.name) return false
if (description != other.description) return false
if (image != null) {
if (other.image == null) return false
if (!image.contentEquals(other.image)) return false
} else if (other.image != null) return false
if (year != other.year) return false
if (sessions != other.sessions) return false
return true
}
override fun hashCode(): Int {
var result = id
result = 31 * result + name.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + (image?.contentHashCode() ?: 0)
result = 31 * result + year.hashCode()
result = 31 * result + sessions.hashCode()
return result
}
}
fun CinemaWithSessionsRemote.toCinemaWithSessions(): CinemaWithSessions = CinemaWithSessions(
Cinema(
id,
name,
description,
image,
year
),
sessions.map { x -> x.toSessionFromCinema() }
)
fun Cinema.toCinemaWithSessionsRemote(): CinemaWithSessionsRemote = CinemaWithSessionsRemote(
uid,
name,
description,
image,
year,
sessions = emptyList()
)*/

View File

@ -0,0 +1,51 @@
package com.example.myapplication.api.session
import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.repository.OfflineOrderSessionRepository
import com.example.myapplication.database.entities.repository.OfflineSessionRepository
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
import com.example.myapplication.database.entities.repository.SessionRepository
class RestSessionRepository(
private val service: MyServerService,
private val dbSessionRepository: OfflineSessionRepository,
private val dbUserSessionRepository: OfflineUserSessionRepository,
private val dbOrderSessionRepository: OfflineOrderSessionRepository,
) : SessionRepository {
override suspend fun getSession(uid: Int): Session {
return service.getSession(uid).toSession()
}
override suspend fun insertSession(session: Session) {
dbSessionRepository.insertSession(
service.createSession(session.toSessionRemote()).toSession()
)
}
override suspend fun updateSession(session: Session) {
dbSessionRepository.updateSession(
service.updateSession(
session.uid,
session.toSessionRemote()
).toSession()
)
}
override suspend fun deleteSession(session: Session) {
val cart = service.getUsers()
cart.forEach { userRemote ->
userRemote.sessions = userRemote.sessions.filter { x -> x.id != session.uid }
service.updateUserCart(userRemote.id, userRemote)
}
val orders = service.getOrders()
orders.forEach { orderRemote ->
orderRemote.sessions = orderRemote.sessions.filter { x -> x.id != session.uid }
service.updateOrder(orderRemote.id, orderRemote)
}
service.deleteSession(session.uid)
dbUserSessionRepository.deleteSessionsByUid(session.uid)
dbOrderSessionRepository.deleteSessionsByUid(session.uid)
dbSessionRepository.deleteSession(session)
}
}

View File

@ -0,0 +1,20 @@
package com.example.myapplication.api.session
import com.example.myapplication.api.cinema.CinemaRemote
import com.example.myapplication.api.cinema.toCinema
import com.example.myapplication.database.entities.model.SessionFromCart
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
class SessionFromCartRemote(
val id: Int = 0,
var count: Int = 0,
var cinemaId: Int = 0,
)
fun SessionFromCartRemote.toSessionFromCart(cinema: CinemaRemote, dateTime: LocalDateTime, price: Double, availableCount: Int): SessionFromCart =
SessionFromCart(
id, dateTime, price, availableCount, count, cinema.id, cinema.toCinema()
)

View File

@ -0,0 +1,34 @@
package com.example.myapplication.api.session
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCinema
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
class SessionFromCinemaRemote(
val id: Int = 0,
@Contextual
val dateTime: LocalDateTime = LocalDateTime.MIN,
val price: Double = 0.0,
val maxCount: Int = 0,
val availableCount: Int = 0,
val cinemaId: Int = 0,
)
fun SessionFromCinemaRemote.toSessionFromCinema(): SessionFromCinema = SessionFromCinema(
id,
dateTime,
price,
availableCount,
cinemaId
)
fun SessionFromCinema.toSessionFromCinemaRemote(): SessionFromCinemaRemote = SessionFromCinemaRemote(
uid,
dateTime,
price,
availableCount,
cinemaId
)

View File

@ -0,0 +1,22 @@
package com.example.myapplication.api.session
import com.example.myapplication.api.cinema.CinemaRemote
import com.example.myapplication.api.cinema.toCinema
import com.example.myapplication.database.entities.model.SessionFromOrder
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
class SessionFromOrderRemote(
val id: Int = 0,
@Contextual val dateTime: LocalDateTime = LocalDateTime.MIN,
val frozenPrice: Double = 0.0,
val count: Int = 0,
val cinemaId: Int = 0,
)
fun SessionFromOrderRemote.toSessionFromOrder(cinema: CinemaRemote): SessionFromOrder =
SessionFromOrder(
id, dateTime, frozenPrice, count, cinemaId, cinema.toCinema()
)

View File

@ -0,0 +1,34 @@
package com.example.myapplication.api.session
import com.example.myapplication.api.cinema.CinemaRemote
import com.example.myapplication.api.cinema.toCinema
import com.example.myapplication.database.entities.model.Session
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
data class SessionRemote(
val id: Int = 0,
@Contextual
val dateTime: LocalDateTime,
val price: Double,
val maxCount: Int,
val cinemaId: Int = 0
)
fun SessionRemote.toSession(): Session = Session(
id,
dateTime,
price,
maxCount,
cinemaId
)
fun Session.toSessionRemote(): SessionRemote = SessionRemote(
uid,
dateTime,
price,
maxCount,
cinemaId
)

View File

@ -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
)

View File

@ -0,0 +1,57 @@
package com.example.myapplication.api.user
import android.util.Log
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.session.toSessionFromCart
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.model.UserSessionCrossRef
import com.example.myapplication.database.entities.repository.OfflineUserRepository
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
import com.example.myapplication.database.entities.repository.UserRepository
import kotlinx.coroutines.flow.Flow
class RestUserRepository(
private val service: MyServerService,
private val dbUserRepository: OfflineUserRepository,
private val dbUserSessionRepository: OfflineUserSessionRepository,
) : UserRepository {
override fun getAllUsers(): Flow<List<User>> {
Log.d(RestUserRepository::class.simpleName, "Get users")
return dbUserRepository.getAllUsers()
}
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> {
val cart = service.getUserCart(userId)
dbUserSessionRepository.deleteUserSessions(userId)
cart.sessions.map { sessionFromCartRemote ->
dbUserSessionRepository.insertUserSession(
UserSessionCrossRef(
userId,
sessionFromCartRemote.id,
sessionFromCartRemote.count
)
)
}
return cart.sessions.map {
val session = service.getSession(it.id)
it.toSessionFromCart(
session.cinema,
session.dateTime,
session.price,
session.maxCount - service.getOrders().flatMap { order ->
order.sessions.filter { session -> session.id == it.id }
}.sumOf { session -> session.count })
}
}
override suspend fun insertUser(user: User) {
}
override suspend fun updateUser(user: User) {
}
override suspend fun deleteUser(user: User) {
}
}

View File

@ -0,0 +1,12 @@
package com.example.myapplication.api.user
import com.example.myapplication.api.session.SessionFromCartRemote
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int = 0,
val login: String = "",
val password: String = "",
var sessions: List<SessionFromCartRemote> = emptyList()
)

View File

@ -0,0 +1,62 @@
package com.example.myapplication.api.usersession
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.session.SessionFromCartRemote
import com.example.myapplication.api.session.toSession
import com.example.myapplication.database.entities.model.UserSessionCrossRef
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository
class RestUserSessionRepository(
private val service: MyServerService,
private val dbUserSessionRepository: OfflineUserSessionRepository
) : UserSessionRepository {
override suspend fun insertUserSession(userSessionCrossRef: UserSessionCrossRef) {
var cartSessions = service.getUserCart(userSessionCrossRef.userId)
cartSessions.sessions.forEach { session ->
if (session.id == userSessionCrossRef.sessionId)
return
}
val session = service.getSession(userSessionCrossRef.sessionId).toSession()
val sessionFromCart = SessionFromCartRemote(
session.uid,
userSessionCrossRef.count,
session.cinemaId,
)
val updatedSessions = cartSessions.sessions.toMutableList()
updatedSessions.add(sessionFromCart)
cartSessions = cartSessions.copy(sessions = updatedSessions)
service.updateUserCart(userSessionCrossRef.userId, cartSessions)
dbUserSessionRepository.insertUserSession(userSessionCrossRef)
}
override suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef) {
val userRemote = service.getUserCart(userSessionCrossRef.userId)
if (userSessionCrossRef.count <= 0) {
userRemote.sessions =
userRemote.sessions.filter { x -> x.id != userSessionCrossRef.sessionId }
} else
userRemote.sessions.forEach {
if (it.id == userSessionCrossRef.sessionId) {
it.count = userSessionCrossRef.count
}
}
service.updateUserCart(userSessionCrossRef.userId, userRemote)
dbUserSessionRepository.updateUserSession(userSessionCrossRef)
}
override suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef) {
updateUserSession(userSessionCrossRef)
dbUserSessionRepository.deleteUserSession(userSessionCrossRef)
}
override suspend fun deleteUserSessions(userId: Int) {
val userRemote = service.getUserCart(userId)
userRemote.sessions = emptyList()
service.updateUserCart(userId, userRemote)
dbUserSessionRepository.deleteUserSessions(userId)
}
}

View File

@ -34,7 +34,7 @@ import androidx.compose.material3.SwipeToDismiss
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -58,6 +58,7 @@ import com.example.myapplication.database.entities.composeui.CartViewModel
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter
@ -66,7 +67,11 @@ fun Cart(
viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val cartUiState by viewModel.cartUiState.collectAsState()
val cartUiState = viewModel.cartUiState
LaunchedEffect(Unit) {
viewModel.refreshState()
}
Cart(
cartUiState = cartUiState,
@ -276,7 +281,13 @@ private fun SessionListItem(
)
IconButton(
onClick = { onChangeCount(session, 1, ++currentCount) }
onClick = {
onChangeCount(
session,
1,
if (currentCount != session.availableCount) ++currentCount else currentCount
)
}
) {
Icon(
imageVector = Icons.Default.Add,

View File

@ -1,49 +1,101 @@
package com.example.myapplication.database
import android.content.Context
import com.example.myapplication.database.entities.repository.CinemaRepository
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.entities.repository.OrderRepository
import com.example.myapplication.database.entities.repository.OrderSessionRepository
import com.example.myapplication.database.entities.repository.SessionRepository
import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
interface AppContainer {
val cinemaRepository: CinemaRepository
val orderRepository: OrderRepository
val orderSessionRepository: OrderSessionRepository
val sessionRepository: SessionRepository
val userRepository: UserRepository
val userSessionRepository: UserSessionRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val cinemaRepository: CinemaRepository by lazy {
OfflineCinemaRepository(AppDatabase.getInstance(context).cinemaDao())
}
override val orderRepository: OrderRepository by lazy {
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
}
override val orderSessionRepository: OrderSessionRepository by lazy {
OfflineOrderSessionRepository(AppDatabase.getInstance(context).orderSessionCrossRefDao())
}
override val sessionRepository: SessionRepository by lazy {
OfflineSessionRepository(AppDatabase.getInstance(context).sessionDao())
}
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
override val userSessionRepository: UserSessionRepository by lazy {
OfflineUserSessionRepository(AppDatabase.getInstance(context).userSessionCrossRefDao())
}
val cinemaRestRepository: RestCinemaRepository
val sessionRestRepository: RestSessionRepository
val userRestRepository: RestUserRepository
val orderRestRepository: RestOrderRepository
val orderSessionRestRepository: RestOrderSessionRepository
val userSessionRestRepository: RestUserSessionRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
private val cinemaRepository: OfflineCinemaRepository by lazy {
OfflineCinemaRepository(AppDatabase.getInstance(context).cinemaDao())
}
private val orderRepository: OfflineOrderRepository by lazy {
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
}
private val orderSessionRepository: OfflineOrderSessionRepository by lazy {
OfflineOrderSessionRepository(AppDatabase.getInstance(context).orderSessionCrossRefDao())
}
private val sessionRepository: OfflineSessionRepository by lazy {
OfflineSessionRepository(AppDatabase.getInstance(context).sessionDao())
}
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val userSessionRepository: OfflineUserSessionRepository by lazy {
OfflineUserSessionRepository(AppDatabase.getInstance(context).userSessionCrossRefDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
}
override val cinemaRestRepository: RestCinemaRepository by lazy {
RestCinemaRepository(
MyServerService.getInstance(),
cinemaRepository,
sessionRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val sessionRestRepository: RestSessionRepository by lazy {
RestSessionRepository(
MyServerService.getInstance(),
sessionRepository,
userSessionRepository,
orderSessionRepository,
)
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
MyServerService.getInstance(),
userRepository,
userSessionRepository,
)
}
override val orderRestRepository: RestOrderRepository by lazy {
RestOrderRepository(
MyServerService.getInstance(),
orderRepository,
cinemaRepository,
orderSessionRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val userSessionRestRepository: RestUserSessionRepository by lazy {
RestUserSessionRepository(
MyServerService.getInstance(),
userSessionRepository,
)
}
override val orderSessionRestRepository: RestOrderSessionRepository by lazy {
RestOrderSessionRepository(
MyServerService.getInstance(),
orderSessionRepository,
)
}
}

View File

@ -20,6 +20,8 @@ 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
@ -27,8 +29,15 @@ import org.threeten.bp.LocalDateTime
import java.io.ByteArrayOutputStream
@Database(
entities = [Cinema::class, Session::class, Order::class,
OrderSessionCrossRef::class, User::class, UserSessionCrossRef::class],
entities = [
Cinema::class,
Session::class,
Order::class,
OrderSessionCrossRef::class,
User::class,
UserSessionCrossRef::class,
RemoteKeys::class
],
version = 1,
exportSchema = false
)
@ -40,6 +49,7 @@ abstract class AppDatabase : RoomDatabase() {
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"
@ -51,11 +61,9 @@ abstract class AppDatabase : RoomDatabase() {
INSTANCE?.let { database ->
// Users
val userDao = database.userDao()
val user1 = User(1, "Login", "password")
val user2 = User(2, "Login123", "password123")
val user1 = User(1, "login", "password")
userDao.insert(user1)
userDao.insert(user2)
// Cinemas
/*// Cinemas
val cinemaDao = database.cinemaDao()
val cinema1 =
Cinema(1, "a", "Desc1", createColoredImage(android.graphics.Color.BLUE), 2023)
@ -128,7 +136,7 @@ abstract class AppDatabase : RoomDatabase() {
val userSessionCrossRef1 = UserSessionCrossRef(1, 1, 5)
val userSessionCrossRef2 = UserSessionCrossRef(1, 3, 15)
userSessionCrossRefDao.insert(userSessionCrossRef1)
userSessionCrossRefDao.insert(userSessionCrossRef2)
userSessionCrossRefDao.insert(userSessionCrossRef2)*/
}
}
@ -165,15 +173,15 @@ abstract class AppDatabase : RoomDatabase() {
return stream.toByteArray()
}
fun getRandomColorInt(): Int {
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
}
fun generateCinemaName(index: Int): String {
val base = 'a'.toInt()
private fun generateCinemaName(index: Int): String {
val base = 'a'.code
val alphabetSize = 26
val sb = StringBuilder()
var remainder = index

View File

@ -12,49 +12,49 @@ import com.example.myapplication.database.entities.composeui.edit.SessionEditVie
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
CinemaListViewModel(cinemaApplication().container.cinemaRepository)
CinemaListViewModel(cinemaApplication().container.cinemaRestRepository)
}
initializer {
CinemaEditViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.cinemaRepository
cinemaApplication().container.cinemaRestRepository
)
}
initializer {
CinemaViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.cinemaRepository,
cinemaApplication().container.cinemaRestRepository,
)
}
initializer {
SessionListViewModel(
cinemaApplication().container.sessionRepository,
cinemaApplication().container.userSessionRepository,
cinemaApplication().container.sessionRestRepository,
cinemaApplication().container.userSessionRestRepository,
)
}
initializer {
SessionEditViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.sessionRepository,
cinemaApplication().container.sessionRestRepository,
)
}
initializer {
CartViewModel(
cinemaApplication().container.userSessionRepository,
cinemaApplication().container.orderRepository,
cinemaApplication().container.orderSessionRepository,
cinemaApplication().container.userRepository,
cinemaApplication().container.userSessionRestRepository,
cinemaApplication().container.orderRestRepository,
cinemaApplication().container.orderSessionRestRepository,
cinemaApplication().container.userRestRepository,
)
}
initializer {
OrderListViewModel(
cinemaApplication().container.orderRepository,
cinemaApplication().container.orderRestRepository,
)
}
initializer {
OrderViewModel(
this.createSavedStateHandle(),
cinemaApplication().container.orderRepository,
cinemaApplication().container.orderRestRepository,
)
}
}

View File

@ -1,8 +1,9 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.OrderSessionCrossRef
import com.example.myapplication.database.entities.model.Session
@ -12,10 +13,7 @@ import com.example.myapplication.database.entities.repository.OrderRepository
import com.example.myapplication.database.entities.repository.OrderSessionRepository
import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.delay
class CartViewModel(
private val userSessionRepository: UserSessionRepository,
@ -24,14 +22,13 @@ class CartViewModel(
private val userRepository: UserRepository,
) : ViewModel() {
private val userUid: Int = 1
var cartUiState by mutableStateOf(CartUiState())
private set
val cartUiState: StateFlow<CartUiState> = userRepository.getCartByUser(userUid).map {
CartUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = CartUiState()
)
suspend fun refreshState() {
val cart = userRepository.getCartByUser(userUid)
cartUiState = CartUiState(cart)
}
suspend fun addToOrder(userId: Int, sessions: List<SessionFromCart>) {
if (sessions.isEmpty())
@ -48,10 +45,12 @@ class CartViewModel(
)
}
userSessionRepository.deleteUserSessions(userId)
refreshState()
}
suspend fun removeFromCart(user: Int, session: Session, count: Int = 1) {
userSessionRepository.deleteUserSession(UserSessionCrossRef(user, session.uid, count))
refreshState()
}
suspend fun updateFromCart(userId: Int, session: Session, count: Int, availableCount: Int)
@ -63,6 +62,7 @@ class CartViewModel(
if (count > availableCount)
return false
userSessionRepository.updateUserSession(UserSessionCrossRef(userId, session.uid, count))
refreshState()
return true
}
}

View File

@ -46,17 +46,7 @@ fun CinemaList(
viewModel: CinemaListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val cinemaPagingItems = viewModel.cinemaPagerState.cinemaPagingData.collectAsLazyPagingItems()
fun findCinemas() {
coroutineScope.launch {
viewModel.findCinemas()
}
}
LaunchedEffect(1) {
findCinemas()
}
val cinemaPagingItems = viewModel.cinemaListUiState.collectAsLazyPagingItems()
Scaffold(
topBar = {},
@ -132,7 +122,6 @@ private fun CinemaList(
}
}
}
}
}

View File

@ -15,29 +15,9 @@ import kotlinx.coroutines.flow.emptyFlow
class CinemaListViewModel(
private val cinemaRepository: CinemaRepository
) : ViewModel() {
private val pagingConfig = PagingConfig(
pageSize = 4,
prefetchDistance = 4
)
var cinemaPagerState by mutableStateOf(CinemaPagerState())
private set
fun findCinemas() {
val pager = Pager(
config = pagingConfig,
pagingSourceFactory = {
cinemaRepository.getAllCinemasPaged()
}
)
cinemaPagerState = CinemaPagerState(pager.flow)
}
val cinemaListUiState: Flow<PagingData<Cinema>> = cinemaRepository.getAllCinemas()
suspend fun deleteCinema(cinema: Cinema) {
cinemaRepository.deleteCinema(cinema)
}
}
data class CinemaPagerState(
val cinemaPagingData: Flow<PagingData<Cinema>> = emptyFlow()
)

View File

@ -19,8 +19,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
@ -38,7 +37,11 @@ fun CinemaView(
navController: NavController,
viewModel: CinemaViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val cinemaUiState by viewModel.cinemaUiState.collectAsState()
val cinemaUiState = viewModel.cinemaUiState
LaunchedEffect(Unit) {
viewModel.refreshState()
}
Column(
modifier = Modifier
@ -131,7 +134,7 @@ fun CinemaView(
}
}
if (cinemaUiState.cinemaWithSessions != null) {
SessionList(cinemaUiState.cinemaWithSessions!!, navController)
SessionList(viewModel, navController)
}
}
}

View File

@ -1,31 +1,45 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.CinemaWithSessions
import com.example.myapplication.database.entities.repository.CinemaRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class CinemaViewModel(
savedStateHandle: SavedStateHandle,
private val cinemaRepository: CinemaRepository
savedStateHandle: SavedStateHandle, private val cinemaRepository: CinemaRepository
) : ViewModel() {
private val cinemaUid: Int = checkNotNull(savedStateHandle["id"])
val cinemaUiState: StateFlow<CinemaUiState> = cinemaRepository.getCinema(
cinemaUid
).map {
CinemaUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = CinemaUiState()
)
var cinemaUiState by mutableStateOf(CinemaUiState())
private set
suspend fun refreshState() {
if (cinemaUid > 0) {
cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
}
}
// init {
// viewModelScope.launch {
// if (cinemaUid > 0) {
// cinemaUiState = CinemaUiState(cinemaRepository.getCinema(cinemaUid))
// }
// }
// }
// val cinemaUiState: mutableStateOf(CinemaUiState()) = cinemaRepository.getCinema(
// cinemaUid
// ).map
// {
// CinemaUiState(it)
// }.stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
// initialValue = CinemaUiState()
// )
}
data class CinemaUiState(val cinemaWithSessions: CinemaWithSessions? = null)

View File

@ -25,6 +25,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.ui.theme.PmudemoTheme
@ -35,14 +38,17 @@ fun OrderList(
viewModel: OrderListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val ordersUiState by viewModel.orderListUiState.collectAsState()
val ordersUiState = viewModel.orderListUiState.collectAsLazyPagingItems()
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
) {
items(ordersUiState.orderList) { order ->
val orderId = Screen.OrderView.route.replace("{id}", order.uid.toString())
items(count = ordersUiState.itemCount,
key = ordersUiState.itemKey(),
contentType = ordersUiState.itemContentType()) { index ->
val order = ordersUiState[index]
val orderId = Screen.OrderView.route.replace("{id}", order!!.uid.toString())
Box(
modifier = Modifier
.fillMaxWidth()

View File

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

View File

@ -3,11 +3,13 @@ package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.SessionFromOrder
import com.example.myapplication.database.entities.repository.OrderRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@ -16,11 +18,12 @@ class OrderViewModel(
private val orderRepository: OrderRepository
) : ViewModel() {
private val orderUid: Int = checkNotNull(savedStateHandle["id"])
val orderUiState: StateFlow<OrderUiState> = orderRepository.getOrder(orderUid).map {
val orderUiState: StateFlow<OrderUiState> = flow{emit(orderRepository.getOrder(orderUid))} .map {
OrderUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppContainer.TIMEOUT),
initialValue = OrderUiState()
)
}

View File

@ -33,17 +33,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.CinemaWithSessions
import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun SessionList(
cinemaWithSessions: CinemaWithSessions,
cinemaWithSessionsViewModel: CinemaViewModel,
navController: NavController,
viewModel: SessionListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val cinemaWithSessions = cinemaWithSessionsViewModel.cinemaUiState.cinemaWithSessions!!
LazyColumn {
if (cinemaWithSessions.sessions.isEmpty()) {
item {
@ -107,6 +108,7 @@ fun SessionList(
IconButton(
onClick = {
coroutineScope.launch {
if (session.availableCount != 0)
viewModel.addSessionInCart(sessionId = session.uid)
}
},
@ -122,6 +124,7 @@ fun SessionList(
onClick = {
coroutineScope.launch {
viewModel.deleteSession(session = session)
cinemaWithSessionsViewModel.refreshState()
}
},
) {

View File

@ -27,8 +27,6 @@ class CinemaEditViewModel(
viewModelScope.launch {
if (cinemaUid > 0) {
cinemaUiState = cinemaRepository.getCinema(cinemaUid)
.filterNotNull()
.first()
.toUiState(true)
}
}

View File

@ -27,8 +27,6 @@ class SessionEditViewModel(
viewModelScope.launch {
if (sessionUid > 0) {
sessionUiState = sessionRepository.getSession(sessionUid)
.filterNotNull()
.first()
.toUiState(true)
}
}

View File

@ -13,13 +13,10 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface CinemaDao {
@Query("select * from cinemas order by name")
fun getAll(): Flow<List<Cinema>>
@Query("select * from cinemas order by name")
fun getAllCinemasPaged(): PagingSource<Int, Cinema>
fun getAll(): PagingSource<Int, Cinema>
@Query(
"SELECT c.*, s.uid as session_uid, s.date_time, s.price, s.max_count-IFNULL(SUM(os.count), 0) as available_count " +
"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 " +
@ -29,11 +26,14 @@ interface CinemaDao {
fun getByUid(cinemaId: Int?): Flow<Map<Cinema, List<SessionFromCinema>>>
@Insert
suspend fun insert(cinema: Cinema)
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()
}

View File

@ -1,5 +1,6 @@
package com.example.myapplication.database.entities.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
@ -7,12 +8,11 @@ import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.SessionFromOrder
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Query("select * from orders where user_id = :userId")
fun getAll(userId: Int?): Flow<List<Order>>
fun getAll(userId: Int?): PagingSource<Int, Order>
@Query(
"SELECT o.*, s.*, os.count, os.frozen_price " +
@ -21,14 +21,17 @@ interface OrderDao {
"JOIN sessions AS s ON s.uid = os.session_id " +
"WHERE o.uid = :orderId"
)
fun getByUid(orderId: Int?): Flow<List<SessionFromOrder>>
fun getByUid(orderId: Int?): List<SessionFromOrder>
@Insert
suspend fun insert(order: Order): Long
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()
}

View File

@ -3,12 +3,14 @@ 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
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(orderSessionCrossRef: OrderSessionCrossRef)
@Update
@ -16,4 +18,10 @@ interface OrderSessionCrossRefDao {
@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)
}

View File

@ -6,19 +6,30 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Session
import kotlinx.coroutines.flow.Flow
@Dao
interface SessionDao {
@Query("select * from sessions where sessions.uid = :uid")
fun getByUid(uid: Int): Flow<Session>
suspend fun getByUid(uid: Int): Session
@Insert
suspend fun insert(session: Session)
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
}

View File

@ -22,14 +22,17 @@ interface UserDao {
"where users_sessions.user_id = :userId " +
"GROUP BY users_sessions.session_id "
)
fun getCartByUid(userId: Int): Flow<List<SessionFromCart>>
suspend fun getCartByUid(userId: Int): List<SessionFromCart>
@Insert
suspend fun insert(user: User)
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()
}

View File

@ -3,13 +3,14 @@ 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
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(userSessionCrossRef: UserSessionCrossRef)
@Update
@ -20,4 +21,7 @@ interface UserSessionCrossRefDao {
@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)
}

View File

@ -43,7 +43,7 @@ data class Cinema(
if (name != other.name) return false
if (description != other.description) return false
if (year != other.year) return false
return true
return image.contentEquals(other.image)
}
override fun hashCode(): Int {

View File

@ -12,6 +12,8 @@ data class SessionFromCinema(
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
@ -22,6 +24,7 @@ data class SessionFromCinema(
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
}
@ -30,6 +33,15 @@ data class SessionFromCinema(
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
)

View File

@ -6,14 +6,14 @@ import org.threeten.bp.LocalDateTime
data class SessionFromOrder(
@ColumnInfo(name = "uid")
val uid: Int?,
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?,
val cinemaId: Int = 0,
@Relation(
parentColumn = "cinema_id",
entity = Cinema::class,

View File

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

View File

@ -1,28 +1,38 @@
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.flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
class OfflineCinemaRepository(private val cinemaDao: CinemaDao) : CinemaRepository {
override fun getAllCinemas(): Flow<List<Cinema>> = cinemaDao.getAll()
override fun getAllCinemas(): Flow<PagingData<Cinema>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = cinemaDao::getAll
).flow
override fun getAllCinemasPaged(): PagingSource<Int, Cinema> = cinemaDao.getAllCinemasPaged()
override fun getCinema(uid: Int): Flow<CinemaWithSessions> {
return flow {
cinemaDao.getByUid(uid).collect {
emit(it.firstNotNullOf {
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)
@ -30,4 +40,11 @@ class OfflineCinemaRepository(private val cinemaDao: CinemaDao) : CinemaReposito
override suspend fun updateCinema(cinema: Cinema) = cinemaDao.update(cinema)
override suspend fun deleteCinema(cinema: Cinema) = cinemaDao.delete(cinema)
fun getAllCinemasPagingSource(): PagingSource<Int, Cinema> = cinemaDao.getAll()
suspend fun insertCinemas(cinemas: List<Cinema>) =
cinemaDao.insert(*cinemas.toTypedArray())
suspend fun clearCinemas() = cinemaDao.deleteAll()
}

View File

@ -1,18 +1,31 @@
package com.example.myapplication.database.entities.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.entities.dao.OrderDao
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.SessionFromOrder
import kotlinx.coroutines.flow.Flow
class OfflineOrderRepository(private val orderDao: OrderDao) : OrderRepository {
override fun getAllOrders(userId: Int?): Flow<List<Order>> = orderDao.getAll(userId)
override fun getAllOrders(userId: Int?): Flow<PagingData<Order>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { orderDao.getAll(userId) }
).flow
override fun getOrder(orderId: Int?): Flow<List<SessionFromOrder>> = orderDao.getByUid(orderId)
override suspend fun getOrder(uid: Int): List<SessionFromOrder> = orderDao.getByUid(uid)
override suspend fun insertOrder(order: Order): Long = orderDao.insert(order)
override suspend fun insertOrder(order: Order): Long = orderDao.insert(order).first()
override suspend fun updateOrder(order: Order) = orderDao.update(order)
fun getAllOrdersPagingSource(userId: Int?): PagingSource<Int, Order> = orderDao.getAll(userId)
override suspend fun deleteOrder(order: Order) = orderDao.delete(order)
suspend fun clearOrders() = orderDao.deleteAll()
suspend fun insertOrders(orders: List<Order>) = orderDao.insert(*orders.toTypedArray())
}

View File

@ -13,4 +13,8 @@ class OfflineOrderSessionRepository(private val orderSessionDao: OrderSessionCro
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)
}

View File

@ -2,14 +2,18 @@ package com.example.myapplication.database.entities.repository
import com.example.myapplication.database.entities.dao.SessionDao
import com.example.myapplication.database.entities.model.Session
import kotlinx.coroutines.flow.Flow
class OfflineSessionRepository(private val sessionDao: SessionDao) : SessionRepository {
override fun getSession(uid: Int): Flow<Session?> = sessionDao.getByUid(uid)
override suspend fun 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()
}

View File

@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override fun getCartByUser(userId: Int): Flow<List<SessionFromCart>> =
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> =
userDao.getCartByUid(userId)
override suspend fun insertUser(user: User) = userDao.insert(user)
@ -16,4 +16,9 @@ class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
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()
}

View File

@ -15,4 +15,6 @@ class OfflineUserSessionRepository(private val userSessionDao: UserSessionCrossR
userSessionDao.delete(userSessionCrossRef)
override suspend fun deleteUserSessions(userId: Int) = userSessionDao.deleteByUserUid(userId)
suspend fun deleteSessionsByUid(sessionId: Int) = userSessionDao.deleteBySessionUid(sessionId)
}

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface UserRepository {
fun getAllUsers(): Flow<List<User>>
fun getCartByUser(userId: Int): Flow<List<SessionFromCart>>
suspend fun getCartByUser(userId: Int): List<SessionFromCart>
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)

View File

@ -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)
}

View File

@ -0,0 +1,30 @@
package com.example.myapplication.database.remotekeys.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.model.Session
enum class RemoteKeyType(private val type: String) {
CINEMA(Cinema::class.simpleName ?: "Cinema"),
ORDER(Order::class.simpleName ?: "Order"),
SESSION(Session::class.simpleName ?: "Session");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.154.166</domain>
</domain-config>
</network-security-config>

View File

@ -3,4 +3,5 @@ plugins {
id("com.android.application") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.20" apply false
}

42
server/.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

70088
server/.~data.json Normal file

File diff suppressed because it is too large Load Diff

70088
server/data.json Normal file

File diff suppressed because it is too large Load Diff

1335
server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

12
server/package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "fake-db",
"version": "1.0.0",
"scripts": {
"start": "json-server --watch data.json --host 0.0.0.0 -p 8079"
},
"dependencies": {
},
"devDependencies": {
"json-server": "0.17.4"
}
}

26
server/readme.md Normal file
View File

@ -0,0 +1,26 @@
Установка nodejs:
1. Заходим на сайт https://nodejs.org/en/
2. Скачиваем LTS версию
3. Устанавливаем
Переход в каталог с сервером:
```commandline
cd ./server
```
Установка зависимостей:
```commandline
npm install
```
Запуск:
```commandline
npm start
```
Примеры:
- http://localhost:8079
- http://localhost:8079/students
- http://localhost:8079/students?_expand=group
Документация -- https://www.npmjs.com/package/json-server