отчет

This commit is contained in:
dasha 2023-12-15 19:49:41 +04:00
parent d45b6d7ed8
commit aab18402b5
31 changed files with 171655 additions and 39363 deletions

View File

@ -2,10 +2,14 @@ package com.example.myapplication.api
import com.example.myapplication.api.cinema.CinemaRemote import com.example.myapplication.api.cinema.CinemaRemote
import com.example.myapplication.api.order.OrderRemote import com.example.myapplication.api.order.OrderRemote
import com.example.myapplication.api.cinema.CinemaWithSessionsRemote
import com.example.myapplication.api.session.ReportRemote
import com.example.myapplication.api.session.SessionFromCinemaRemote import com.example.myapplication.api.session.SessionFromCinemaRemote
import com.example.myapplication.api.session.SessionRemote import com.example.myapplication.api.session.SessionRemote
import com.example.myapplication.api.session.SessionWithCinemaRemote import com.example.myapplication.api.session.SessionWithCinemaRemote
import com.example.myapplication.api.user.UserRemote 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 com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
@ -21,9 +25,10 @@ import retrofit2.http.POST
import retrofit2.http.PUT import retrofit2.http.PUT
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
import java.util.Date
interface MyServerService { interface MyServerService {
/*@GET("orders") @GET("orders")
suspend fun getOrders(): List<OrderRemote> suspend fun getOrders(): List<OrderRemote>
@GET("users") @GET("users")
@ -40,6 +45,11 @@ interface MyServerService {
@Path("id") id: Int, @Path("id") id: Int,
): CinemaRemote ): CinemaRemote
@GET("cinemas/{id}?_embed=sessions")
suspend fun getCinemaWithSessions(
@Path("id") id: Int,
): CinemaWithSessionsRemote
@POST("cinemas") @POST("cinemas")
suspend fun createCinema( suspend fun createCinema(
@Body cinema: CinemaRemote, @Body cinema: CinemaRemote,
@ -56,11 +66,6 @@ interface MyServerService {
@Path("id") id: Int, @Path("id") id: Int,
) )
@GET("cinemas/{cinemaId}/sessions")
suspend fun getSessionsForCinema(
@Path("cinemaId") cinemaId: Int
): List<SessionFromCinemaRemote>
@GET("sessions/{id}?_expand=cinema") @GET("sessions/{id}?_expand=cinema")
suspend fun getSession( suspend fun getSession(
@Path("id") id: Int, @Path("id") id: Int,
@ -82,21 +87,40 @@ interface MyServerService {
@Path("id") id: Int, @Path("id") id: Int,
): SessionFromCinemaRemote ): SessionFromCinemaRemote
@GET("users/{id}") @GET("userssessions?_expand=session")
suspend fun getUserCart( suspend fun getUserCart(
@Query("userId") userId: Int,
): List<UserSessionWithSessionRemote>
@GET("userssessions?_expand=session")
suspend fun getUsersSessions(): List<UserSessionWithSessionRemote>
@DELETE("userssessions/{id}")
suspend fun deleteUserSession(
@Path("id") id: Int, @Path("id") id: Int,
): UserRemote ): UserSessionRemote
@GET("users?_limit=1") @GET("users?_limit=1")
suspend fun getUser( suspend fun getUser(
@Query("login") login: String, @Query("login") login: String,
): List<UserRemote> ): List<UserRemote>
@PUT("users/{id}") @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( suspend fun updateUserCart(
@Path("id") id: Int, @Path("id") id: Int,
@Body userRemote: UserRemote, @Body userSessionRemote: UserSessionRemote,
): UserRemote ): UserSessionRemote
@POST("users") @POST("users")
suspend fun createUser( suspend fun createUser(
@ -105,6 +129,7 @@ interface MyServerService {
@GET("orders") @GET("orders")
suspend fun getOrders( suspend fun getOrders(
@Query("userId") userId: Int,
@Query("_page") page: Int, @Query("_page") page: Int,
@Query("_limit") limit: Int, @Query("_limit") limit: Int,
): List<OrderRemote> ): List<OrderRemote>
@ -123,11 +148,17 @@ interface MyServerService {
suspend fun updateOrder( suspend fun updateOrder(
@Path("id") id: Int, @Path("id") id: Int,
@Body orderRemote: OrderRemote, @Body orderRemote: OrderRemote,
): OrderRemote*/ ): OrderRemote
@GET("report")
suspend fun getReport(
@Query("startDate") startDate: Date,
@Query("endDate") endDate: Date
): List<ReportRemote>
companion object { companion object {
//private const val BASE_URL = "http://192.168.154.166:8080/" //private const val BASE_URL = "http://192.168.154.166:8080/"
private const val BASE_URL = "http://192.168.0.101:8080/" private const val BASE_URL = "http://192.168.0.101:8079/"
@Volatile @Volatile
private var INSTANCE: MyServerService? = null private var INSTANCE: MyServerService? = null

View File

@ -1,12 +1,12 @@
package com.example.myapplication.api.cinema package com.example.myapplication.api.cinema
import android.database.sqlite.SQLiteConstraintException
import androidx.paging.ExperimentalPagingApi import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType import androidx.paging.LoadType
import androidx.paging.PagingState import androidx.paging.PagingState
import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator
import androidx.room.withTransaction import androidx.room.withTransaction
import com.example.myapplication.api.MyServerService import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.session.toSession
import com.example.myapplication.database.AppDatabase import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Cinema import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.repository.OfflineCinemaRepository import com.example.myapplication.database.entities.repository.OfflineCinemaRepository
@ -55,10 +55,8 @@ class CinemaRemoteMediator(
try { try {
val cinemas = service.getCinemas(page, state.config.pageSize).map { it.toCinema() } val cinemas = service.getCinemas(page, state.config.pageSize).map { it.toCinema() }
val cinemasWithSessions = cinemas.map { cinema -> val sessionsFromCinemas = cinemas.map { cinema ->
service.getSessionsForCinema(cinema.uid).map { service.getCinemaWithSessions(cinema.uid).toSessions()
service.getSession(it.id).toSession()
}
} }
val endOfPaginationReached = cinemas.isEmpty() val endOfPaginationReached = cinemas.isEmpty()
database.withTransaction { database.withTransaction {
@ -79,10 +77,11 @@ class CinemaRemoteMediator(
} }
dbRemoteKeyRepository.createRemoteKeys(keys) dbRemoteKeyRepository.createRemoteKeys(keys)
dbCinemaRepository.insertCinemas(cinemas) dbCinemaRepository.insertCinemas(cinemas)
cinemasWithSessions.forEach { sessionsFromCinemas.forEach {
try { try {
dbSessionRepository.insertSessions(it) dbSessionRepository.insertSessions(it)
} catch (_: Exception) { } catch(_:Exception) {
} }
} }
} }
@ -91,6 +90,8 @@ class CinemaRemoteMediator(
return MediatorResult.Error(exception) return MediatorResult.Error(exception)
} catch (exception: HttpException) { } catch (exception: HttpException) {
return MediatorResult.Error(exception) return MediatorResult.Error(exception)
} catch (exception: SQLiteConstraintException) {
return MediatorResult.Error(exception)
} }
} }

View File

@ -0,0 +1,31 @@
package com.example.myapplication.api.cinema
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.Session
import com.example.myapplication.database.entities.model.toSession
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
)
fun CinemaWithSessionsRemote.toSessions(): List<Session> =
sessions.map { it.toSessionFromCinema().toSession() }

View File

@ -47,20 +47,25 @@ class RestCinemaRepository(
} }
override suspend fun getCinema(uid: Int): CinemaWithSessions { override suspend fun getCinema(uid: Int): CinemaWithSessions {
val cinema = service.getCinema(uid).toCinema() val cinemaWithSessions = service.getCinemaWithSessions(uid)
val sessions = service.getSessionsForCinema(uid).map { x -> val sessions = cinemaWithSessions.sessions.map { sessionFromCinemaRemote ->
SessionFromCinema( SessionFromCinema(
x.id, sessionFromCinemaRemote.id,
x.dateTime, sessionFromCinemaRemote.dateTime,
x.price, sessionFromCinemaRemote.price,
x.maxCount - service.getOrders().flatMap { order -> sessionFromCinemaRemote.maxCount - service.getOrders().flatMap
order.sessions.filter { session -> session.id == x.id } { order ->
order.sessions.filter { session ->
session.id == sessionFromCinemaRemote.id &&
session.cinemaId == sessionFromCinemaRemote.cinemaId &&
session.cinema.name == cinemaWithSessions.name
}
}.sumOf { session -> session.count }, }.sumOf { session -> session.count },
uid uid
) )
} }
return CinemaWithSessions(cinema, sessions) return CinemaWithSessions(cinemaWithSessions.toCinema(), sessions)
} }
override suspend fun insertCinema(cinema: Cinema) { override suspend fun insertCinema(cinema: Cinema) {
@ -72,15 +77,11 @@ class RestCinemaRepository(
} }
override suspend fun deleteCinema(cinema: Cinema) { override suspend fun deleteCinema(cinema: Cinema) {
val cart = service.getUsers() val cart = service.getUsersSessions()
cart.forEach { userRemote -> cart.forEach { userSessionRemote ->
userRemote.sessions = userRemote.sessions.filter { x -> x.cinemaId != cinema.uid } if (userSessionRemote.session.cinemaId == cinema.uid) {
service.updateUserCart(userRemote.id, userRemote) service.deleteUserSession(userSessionRemote.id)
} }
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) service.deleteCinema(cinema.uid)
dbCinemaRepository.deleteCinema(cinema) dbCinemaRepository.deleteCinema(cinema)

View File

@ -5,6 +5,7 @@ import androidx.paging.LoadType
import androidx.paging.PagingState import androidx.paging.PagingState
import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator
import androidx.room.withTransaction import androidx.room.withTransaction
import com.example.myapplication.LiveStore
import com.example.myapplication.api.MyServerService import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.AppDatabase import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Order import com.example.myapplication.database.entities.model.Order
@ -51,7 +52,8 @@ class OrderRemoteMediator(
} }
try { try {
val orders = service.getOrders(page, state.config.pageSize).map { it.toOrder() } val orders = service.getOrders(LiveStore.user.value?.uid ?: 0,
page, state.config.pageSize).map { it.toOrder() }
val endOfPaginationReached = orders.isEmpty() val endOfPaginationReached = orders.isEmpty()
database.withTransaction { database.withTransaction {
if (loadType == LoadType.REFRESH) { if (loadType == LoadType.REFRESH) {

View File

@ -63,7 +63,7 @@ class RestOrderRepository(
) )
) )
} }
return order.sessions.map { x -> x.toSessionFromOrder(dbCinemaRepository.getCinema(x.cinemaId).cinema.toCinemaRemote()) } return order.sessions.map { x -> x.toSessionFromOrder() }
} }
override suspend fun insertOrder(order: Order): Long { override suspend fun insertOrder(order: Order): Long {

View File

@ -13,14 +13,15 @@ class RestOrderSessionRepository(
) : OrderSessionRepository { ) : OrderSessionRepository {
override suspend fun insertOrderSession(orderSessionCrossRef: OrderSessionCrossRef) { override suspend fun insertOrderSession(orderSessionCrossRef: OrderSessionCrossRef) {
var orderRemote = service.getOrder(orderSessionCrossRef.orderId) var orderRemote = service.getOrder(orderSessionCrossRef.orderId)
val session = service.getSession(orderSessionCrossRef.sessionId).toSession() val session = service.getSession(orderSessionCrossRef.sessionId)
val sessionFromOrder = SessionFromOrderRemote( val sessionFromOrder = SessionFromOrderRemote(
session.uid, session.id,
session.dateTime, session.dateTime,
session.price, session.price,
orderSessionCrossRef.count, orderSessionCrossRef.count,
session.cinemaId session.cinemaId,
session.cinema
) )
val updatedSessions = orderRemote.sessions.toMutableList() val updatedSessions = orderRemote.sessions.toMutableList()

View File

@ -1,71 +0,0 @@
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,21 @@
package com.example.myapplication.api.session
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ReportRemote(
@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
)

View File

@ -6,6 +6,7 @@ import com.example.myapplication.database.entities.repository.OfflineOrderSessio
import com.example.myapplication.database.entities.repository.OfflineSessionRepository import com.example.myapplication.database.entities.repository.OfflineSessionRepository
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
import com.example.myapplication.database.entities.repository.SessionRepository import com.example.myapplication.database.entities.repository.SessionRepository
import java.util.Date
class RestSessionRepository( class RestSessionRepository(
private val service: MyServerService, private val service: MyServerService,
@ -33,10 +34,11 @@ class RestSessionRepository(
} }
override suspend fun deleteSession(session: Session) { override suspend fun deleteSession(session: Session) {
val cart = service.getUsers() val cart = service.getUsersSessions()
cart.forEach { userRemote -> cart.forEach { userSessionRemote ->
userRemote.sessions = userRemote.sessions.filter { x -> x.id != session.uid } if (userSessionRemote.session.id == session.uid) {
service.updateUserCart(userRemote.id, userRemote) service.deleteUserSession(userSessionRemote.id)
}
} }
val orders = service.getOrders() val orders = service.getOrders()
orders.forEach { orderRemote -> orders.forEach { orderRemote ->
@ -48,4 +50,8 @@ class RestSessionRepository(
dbOrderSessionRepository.deleteSessionsByUid(session.uid) dbOrderSessionRepository.deleteSessionsByUid(session.uid)
dbSessionRepository.deleteSession(session) dbSessionRepository.deleteSession(session)
} }
suspend fun getReport(startDate: Date, endDate: Date): List<ReportRemote> {
return service.getReport(startDate, endDate)
}
} }

View File

@ -1,6 +1,5 @@
package com.example.myapplication.api.session package com.example.myapplication.api.session
import com.example.myapplication.database.entities.model.Session
import com.example.myapplication.database.entities.model.SessionFromCinema import com.example.myapplication.database.entities.model.SessionFromCinema
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -23,12 +22,4 @@ fun SessionFromCinemaRemote.toSessionFromCinema(): SessionFromCinema = SessionFr
price, price,
availableCount, availableCount,
cinemaId cinemaId
)
fun SessionFromCinema.toSessionFromCinemaRemote(): SessionFromCinemaRemote = SessionFromCinemaRemote(
uid,
dateTime,
price,
availableCount,
cinemaId
) )

View File

@ -14,9 +14,10 @@ class SessionFromOrderRemote(
val frozenPrice: Double = 0.0, val frozenPrice: Double = 0.0,
val count: Int = 0, val count: Int = 0,
val cinemaId: Int = 0, val cinemaId: Int = 0,
val cinema: CinemaRemote,
) )
fun SessionFromOrderRemote.toSessionFromOrder(cinema: CinemaRemote): SessionFromOrder = fun SessionFromOrderRemote.toSessionFromOrder(): SessionFromOrder =
SessionFromOrder( SessionFromOrder(
id, dateTime, frozenPrice, count, cinemaId, cinema.toCinema() id, dateTime, frozenPrice, count, cinemaId, cinema.toCinema()
) )

View File

@ -30,25 +30,21 @@ class RestUserRepository(
override suspend fun getCartByUser(userId: Int): List<SessionFromCart> { override suspend fun getCartByUser(userId: Int): List<SessionFromCart> {
val cart = service.getUserCart(userId) val cart = service.getUserCart(userId)
dbUserSessionRepository.deleteUserSessions(userId) dbUserSessionRepository.deleteUserSessions(userId)
cart.sessions.map { sessionFromCartRemote -> cart.map { sessionFromCartRemote ->
dbUserSessionRepository.insertUserSession( dbUserSessionRepository.insertUserSession(
UserSessionCrossRef( UserSessionCrossRef(
userId, userId,
sessionFromCartRemote.id, sessionFromCartRemote.sessionId,
sessionFromCartRemote.count sessionFromCartRemote.count
) )
) )
} }
return cart.map {
return cart.sessions.map { val cinema = service.getCinema(it.session.cinemaId)
val session = service.getSession(it.id)
it.toSessionFromCart( it.toSessionFromCart(
session.cinema, it.session.maxCount - service.getOrders().flatMap { order ->
session.dateTime,
session.price,
session.maxCount - service.getOrders().flatMap { order ->
order.sessions.filter { session -> session.id == it.id } order.sessions.filter { session -> session.id == it.id }
}.sumOf { session -> session.count }) }.sumOf { session -> session.count }, cinema.toCinema())
} }
} }

View File

@ -13,7 +13,6 @@ data class UserRemote(
val login: String = "", val login: String = "",
val password: String = "", val password: String = "",
val role: Int = -1, val role: Int = -1,
var sessions: List<SessionFromCartRemote> = emptyList()
) )
fun User.toUserRemote(): UserRemote = UserRemote( fun User.toUserRemote(): UserRemote = UserRemote(

View File

@ -0,0 +1,15 @@
package com.example.myapplication.api.user
import com.example.myapplication.api.session.SessionRemote
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.SessionFromCart
import com.example.myapplication.database.entities.model.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,
)

View File

@ -0,0 +1,25 @@
package com.example.myapplication.api.user
import com.example.myapplication.api.session.SessionRemote
import com.example.myapplication.database.entities.model.Cinema
import com.example.myapplication.database.entities.model.SessionFromCart
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,
)
fun UserSessionWithSessionRemote.toSessionFromCart(availableCount: Int = 0, cinema: Cinema): SessionFromCart = SessionFromCart(
sessionId,
session.dateTime,
session.price,
availableCount,
count,
session.cinemaId,
cinema
)

View File

@ -3,6 +3,7 @@ package com.example.myapplication.api.usersession
import com.example.myapplication.api.MyServerService import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.session.SessionFromCartRemote import com.example.myapplication.api.session.SessionFromCartRemote
import com.example.myapplication.api.session.toSession import com.example.myapplication.api.session.toSession
import com.example.myapplication.api.user.UserSessionRemote
import com.example.myapplication.database.entities.model.UserSessionCrossRef import com.example.myapplication.database.entities.model.UserSessionCrossRef
import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository import com.example.myapplication.database.entities.repository.OfflineUserSessionRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository import com.example.myapplication.database.entities.repository.UserSessionRepository
@ -12,51 +13,44 @@ class RestUserSessionRepository(
private val dbUserSessionRepository: OfflineUserSessionRepository private val dbUserSessionRepository: OfflineUserSessionRepository
) : UserSessionRepository { ) : UserSessionRepository {
override suspend fun insertUserSession(userSessionCrossRef: UserSessionCrossRef) { override suspend fun insertUserSession(userSessionCrossRef: UserSessionCrossRef) {
var cartSessions = service.getUserCart(userSessionCrossRef.userId) val cartSessions = service.getUserCart(userSessionCrossRef.userId)
cartSessions.sessions.forEach { session -> cartSessions.forEach { session ->
if (session.id == userSessionCrossRef.sessionId) if (session.sessionId == userSessionCrossRef.sessionId)
return return
} }
val session = service.getSession(userSessionCrossRef.sessionId).toSession() service.createUserSession(UserSessionRemote(id = 0,
userId = userSessionCrossRef.userId,
val sessionFromCart = SessionFromCartRemote( sessionId = userSessionCrossRef.sessionId,
session.uid, count = userSessionCrossRef.count
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) dbUserSessionRepository.insertUserSession(userSessionCrossRef)
} }
override suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef) { override suspend fun updateUserSession(userSessionCrossRef: UserSessionCrossRef) {
val userRemote = service.getUserCart(userSessionCrossRef.userId) val userSessionRemote = service.getUserSession(userSessionCrossRef.userId,
userSessionCrossRef.sessionId).first()
if (userSessionCrossRef.count <= 0) { if (userSessionCrossRef.count <= 0) {
userRemote.sessions = service.deleteUserSession(userSessionRemote.id)
userRemote.sessions.filter { x -> x.id != userSessionCrossRef.sessionId } dbUserSessionRepository.deleteUserSession(userSessionCrossRef)
} else return
userRemote.sessions.forEach { }
if (it.id == userSessionCrossRef.sessionId) { userSessionRemote.count = userSessionCrossRef.count
it.count = userSessionCrossRef.count service.updateUserCart(userSessionRemote.id, userSessionRemote)
}
}
service.updateUserCart(userSessionCrossRef.userId, userRemote)
dbUserSessionRepository.updateUserSession(userSessionCrossRef) dbUserSessionRepository.updateUserSession(userSessionCrossRef)
} }
override suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef) { override suspend fun deleteUserSession(userSessionCrossRef: UserSessionCrossRef) {
updateUserSession(userSessionCrossRef) val userSessionRemote = service.getUserSession(userSessionCrossRef.userId,
userSessionCrossRef.sessionId).first()
service.deleteUserSession(userSessionRemote.id)
dbUserSessionRepository.deleteUserSession(userSessionCrossRef) dbUserSessionRepository.deleteUserSession(userSessionCrossRef)
} }
override suspend fun deleteUserSessions(userId: Int) { override suspend fun deleteUserSessions(userId: Int) {
val userRemote = service.getUserCart(userId) val cart = service.getUserCart(userId)
userRemote.sessions = emptyList() cart.forEach {
service.updateUserCart(userId, userRemote) service.deleteUserSession(it.id)
}
dbUserSessionRepository.deleteUserSessions(userId) dbUserSessionRepository.deleteUserSessions(userId)
} }
} }

View File

@ -15,7 +15,6 @@ fun Authenticator(
dataStoreManager: DataStoreManager, dataStoreManager: DataStoreManager,
viewModel: AuthenticatorViewModel = viewModel(factory = AppViewModelProvider.Factory) viewModel: AuthenticatorViewModel = viewModel(factory = AppViewModelProvider.Factory)
) { ) {
val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val login = dataStoreManager.getLogin().collectAsState(initial = "").value val login = dataStoreManager.getLogin().collectAsState(initial = "").value

View File

@ -164,7 +164,7 @@ private fun SessionListItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onChangeCount: (SessionFromCart, Int) -> Unit, onChangeCount: (SessionFromCart, Int) -> Unit,
) { ) {
var currentCount by remember { mutableIntStateOf(session.count) } //var currentCount by remember { mutableIntStateOf(session.count) }
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm") val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formattedDate = dateFormatter.format(session.dateTime) val formattedDate = dateFormatter.format(session.dateTime)
@ -203,7 +203,7 @@ private fun SessionListItem(
Text( Text(
text = "${session.cinema.name}, ${session.cinema.year}\n" + text = "${session.cinema.name}, ${session.cinema.year}\n" +
"Цена: ${session.price}\n" + "Цена: ${session.price}\n" +
"${currentCount}/${session.availableCount}", "${session.count}/${session.availableCount}",
color = MaterialTheme.colorScheme.onSecondary color = MaterialTheme.colorScheme.onSecondary
) )
} }
@ -219,7 +219,7 @@ private fun SessionListItem(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
IconButton( IconButton(
onClick = { onChangeCount(session, --currentCount) } onClick = { onChangeCount(session, session.count - 1) }
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.minus), imageVector = ImageVector.vectorResource(id = R.drawable.minus),
@ -230,7 +230,7 @@ private fun SessionListItem(
} }
Text( Text(
text = "$currentCount", text = "${session.count}",
color = MaterialTheme.colorScheme.onBackground color = MaterialTheme.colorScheme.onBackground
) )
@ -238,7 +238,7 @@ private fun SessionListItem(
onClick = { onClick = {
onChangeCount( onChangeCount(
session, session,
if (currentCount != session.availableCount) ++currentCount else currentCount if (session.count != session.availableCount) session.count + 1 else session.count
) )
} }
) { ) {

View File

@ -15,6 +15,7 @@ import com.example.myapplication.database.entities.repository.OrderSessionReposi
import com.example.myapplication.database.entities.repository.UserRepository import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserSessionRepository import com.example.myapplication.database.entities.repository.UserSessionRepository
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import java.util.LinkedList
class CartViewModel( class CartViewModel(
private val userSessionRepository: UserSessionRepository, private val userSessionRepository: UserSessionRepository,
@ -22,6 +23,19 @@ class CartViewModel(
private val orderSessionRepository: OrderSessionRepository, private val orderSessionRepository: OrderSessionRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
) : ViewModel() { ) : ViewModel() {
private val requestQueue: LinkedList<suspend () -> Unit> = LinkedList()
private var isProcessingQueue: Boolean = false
private suspend fun processQueue() {
isProcessingQueue = true
while (requestQueue.isNotEmpty()) {
val request = requestQueue.poll()
request.invoke()
refreshState()
}
isProcessingQueue = false
}
var isLoading: Boolean = false var isLoading: Boolean = false
var cartUiState by mutableStateOf(CartUiState()) var cartUiState by mutableStateOf(CartUiState())
private set private set
@ -71,6 +85,7 @@ class CartViewModel(
isLoading = true isLoading = true
val userId: Int = LiveStore.user.value?.uid ?: return false val userId: Int = LiveStore.user.value?.uid ?: return false
if (count == 0) { if (count == 0) {
isLoading = false
removeFromCart(session, count) removeFromCart(session, count)
return false return false
} }

View File

@ -0,0 +1,121 @@
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 com.example.myapplication.api.session.ReportRemote
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(
viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val dateStateStart = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val dateStateEnd = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val coroutineScope = rememberCoroutineScope()
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,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Получить отчет")
}
Spacer(modifier = Modifier.height(16.dp))
CardScreen(reportData = viewModel.reportResultUiState.report)
}
}
@Composable
fun CardScreen(reportData: List<ReportRemote>) {
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
reportData.forEach {
val (cinemaName, ticketDateTime, ticketPrice, ticketQuantity, ticketsPurchased, revenue) = it
Row(
modifier = Modifier
.fillMaxWidth()
.border(width = 1.dp, color = Color.White, 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))
}
}

View File

@ -0,0 +1,56 @@
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 com.example.myapplication.api.session.ReportRemote
import com.example.myapplication.api.session.RestSessionRepository
import java.util.Date
class ReportViewModel(private val serialRepository: RestSessionRepository) : ViewModel() {
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()) {
val temp = serialRepository.getReport(
reportUiState.reportDetails.startDate,
reportUiState.reportDetails.endDate
)
reportResultUiState = ReportResultUiState(temp)
}
}
}
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: List<ReportRemote> = emptyList()
)

View File

@ -19,7 +19,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
@ -29,6 +28,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -46,7 +46,9 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.example.myapplication.LiveStore
import com.example.myapplication.composeui.Cart import com.example.myapplication.composeui.Cart
import com.example.myapplication.composeui.Report
import com.example.myapplication.database.entities.composeui.CinemaList import com.example.myapplication.database.entities.composeui.CinemaList
import com.example.myapplication.database.entities.composeui.CinemaView import com.example.myapplication.database.entities.composeui.CinemaView
import com.example.myapplication.database.entities.composeui.OrderList import com.example.myapplication.database.entities.composeui.OrderList
@ -54,6 +56,7 @@ import com.example.myapplication.database.entities.composeui.OrderView
import com.example.myapplication.database.entities.composeui.UserProfile 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.CinemaEdit
import com.example.myapplication.database.entities.composeui.edit.SessionEdit import com.example.myapplication.database.entities.composeui.edit.SessionEdit
import com.example.myapplication.database.entities.model.UserRole
import com.example.myapplication.datastore.DataStoreManager import com.example.myapplication.datastore.DataStoreManager
@Composable @Composable
@ -136,28 +139,31 @@ fun Navbar(
currentDestination: NavDestination?, currentDestination: NavDestination?,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val user = LiveStore.user.observeAsState()
NavigationBar(modifier = modifier, containerColor = MaterialTheme.colorScheme.primary) { NavigationBar(modifier = modifier, containerColor = MaterialTheme.colorScheme.primary) {
Screen.bottomBarItems.forEach { screen -> Screen.bottomBarItems.forEach { screen ->
NavigationBarItem( if (screen.route != Screen.Report.route || user.value?.role == UserRole.ADMIN) {
icon = { NavigationBarItem(
Icon( icon = {
screen.icon, Icon(
contentDescription = null, screen.icon,
tint = MaterialTheme.colorScheme.secondary contentDescription = null,
) tint = MaterialTheme.colorScheme.secondary
}, )
label = { Text(stringResource(screen.resourceId)) }, },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, label = { Text(stringResource(screen.resourceId)) },
onClick = { selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
navController.navigate(screen.route) { onClick = {
popUpTo(navController.graph.findStartDestination().id) { navController.navigate(screen.route) {
saveState = true popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
} }
launchSingleTop = true
restoreState = true
} }
} )
) }
} }
} }
} }
@ -204,10 +210,10 @@ fun Navhost(
) { backStackEntry -> ) { backStackEntry ->
backStackEntry.arguments?.let { OrderView(it.getInt("id")) } backStackEntry.arguments?.let { OrderView(it.getInt("id")) }
} }
composable(Screen.Report.route) { Report() }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MainNavbar( fun MainNavbar(
isDarkTheme: MutableState<Boolean>, isDarkTheme: MutableState<Boolean>,

View File

@ -41,13 +41,17 @@ enum class Screen(
), ),
UserProfile( UserProfile(
"User-profile", R.string.Profile_title, showInBottomBar = false "User-profile", R.string.Profile_title, showInBottomBar = false
),
Report(
"Report", R.string.Report_title,
); );
companion object { companion object {
val bottomBarItems = listOf( val bottomBarItems = listOf(
CinemaList, CinemaList,
Cart, Cart,
OrderList OrderList,
Report
) )
fun getItem(route: String): Screen? { fun getItem(route: String): Screen? {

View File

@ -9,6 +9,7 @@ import com.example.myapplication.CinemaApplication
import com.example.myapplication.composeui.Authenticator import com.example.myapplication.composeui.Authenticator
import com.example.myapplication.composeui.AuthenticatorViewModel import com.example.myapplication.composeui.AuthenticatorViewModel
import com.example.myapplication.composeui.CartViewModel 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.CinemaEditViewModel
import com.example.myapplication.database.entities.composeui.edit.SessionEditViewModel import com.example.myapplication.database.entities.composeui.edit.SessionEditViewModel
@ -68,6 +69,9 @@ object AppViewModelProvider {
initializer { initializer {
AuthenticatorViewModel(cinemaApplication().container.userRestRepository) AuthenticatorViewModel(cinemaApplication().container.userRestRepository)
} }
initializer {
ReportViewModel(cinemaApplication().container.sessionRestRepository)
}
} }
} }

View File

@ -1,26 +1,18 @@
package com.example.myapplication.database.entities.composeui package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
import com.example.myapplication.LiveStore import com.example.myapplication.LiveStore
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.entities.model.Order import com.example.myapplication.database.entities.model.Order
import com.example.myapplication.database.entities.repository.OrderRepository import com.example.myapplication.database.entities.repository.OrderRepository
import kotlinx.coroutines.flow.Flow 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( class OrderListViewModel(
private val orderRepository: OrderRepository private val orderRepository: OrderRepository
) : ViewModel() { ) : ViewModel() {
val orderListUiState: Flow<PagingData<Order>> = orderRepository.getAllOrders(LiveStore.user.value?.uid ?: 0) val orderListUiState: Flow<PagingData<Order>> =
orderRepository.getAllOrders(LiveStore.user.value?.uid ?: 0)
} }
data class OrderListUiState(val orderList: List<Order> = listOf()) data class OrderListUiState(val orderList: List<Order> = listOf())

View File

@ -46,7 +46,6 @@ fun UserProfile(
viewModel: UserProfileViewModel = viewModel(factory = AppViewModelProvider.Factory), viewModel: UserProfileViewModel = viewModel(factory = AppViewModelProvider.Factory),
) { ) {
var isRegistration by remember { mutableStateOf(false) } var isRegistration by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val coroutine = rememberCoroutineScope() val coroutine = rememberCoroutineScope()
val errorStringId: Int? = viewModel.userUiState.errorId val errorStringId: Int? = viewModel.userUiState.errorId
val errorMessage = if (errorStringId == null) "" else stringResource(errorStringId) val errorMessage = if (errorStringId == null) "" else stringResource(errorStringId)
@ -54,78 +53,65 @@ fun UserProfile(
LazyColumn { LazyColumn {
item { item {
Text( if (user.value != null) {
text = "Текущий пользователь: " + (LiveStore.user.value?.login ?: ""), Column(
)
Button(
enabled = user.value != null,
onClick = {
coroutineScope.launch {
dataStoreManager.setLogin("")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text("Выход")
}
Text(
text = errorMessage,
color = Color.Red
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Логин",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
BasicTextField(
value = viewModel.userUiState.details.login,
onValueChange = {
viewModel.updateUiState(viewModel.userUiState.details.copy(login = it))
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .padding(16.dp),
.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(
text = "Подтверждение пароля", text = "Текущий пользователь: " + (user.value?.login ?: ""),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Button(
enabled = user.value != null,
onClick = {
coroutine.launch {
dataStoreManager.setLogin("")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.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) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
BasicTextField( BasicTextField(
value = viewModel.userUiState.details.passwordConfirm, value = viewModel.userUiState.details.login,
onValueChange = { onValueChange = {
viewModel.updateUiState( viewModel.updateUiState(viewModel.userUiState.details.copy(login = it))
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)
)
Text(
text = "Пароль",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
BasicTextField(
value = viewModel.userUiState.details.password,
onValueChange = {
viewModel.updateUiState(viewModel.userUiState.details.copy(password = it))
}, },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -137,89 +123,110 @@ fun UserProfile(
.padding(start = 13.dp, top = 8.dp), .padding(start = 13.dp, top = 8.dp),
visualTransformation = PasswordVisualTransformation() visualTransformation = PasswordVisualTransformation()
) )
}
if (isRegistration) { if (isRegistration) {
Button( Text(
onClick = { text = "Подтверждение пароля",
coroutineScope.launch { modifier = Modifier.align(Alignment.CenterHorizontally)
val flag = viewModel.signUp() )
isRegistration = !flag BasicTextField(
} value = viewModel.userUiState.details.passwordConfirm,
}, onValueChange = {
modifier = Modifier viewModel.updateUiState(
.fillMaxWidth() viewModel.userUiState.details.copy(
.padding(8.dp) passwordConfirm = it
) { )
Text("Регистрация") )
},
modifier = Modifier
.fillMaxWidth()
.size(36.dp)
.background(
MaterialTheme.colorScheme.secondary,
RoundedCornerShape(18.dp)
)
.padding(start = 13.dp, top = 8.dp),
visualTransformation = PasswordVisualTransformation()
)
} }
Text(
text = "Уже есть аккаунт? Войти", if (isRegistration) {
modifier = Modifier Button(
.clickable { onClick = { coroutine.launch { isRegistration = !viewModel.signUp() } },
isRegistration = false modifier = Modifier
} .fillMaxWidth()
.align(Alignment.CenterHorizontally), .padding(8.dp)
color = MaterialTheme.colorScheme.onBackground ) {
) Text("Регистрация")
} else { }
Button( Text(
onClick = { text = "Уже есть аккаунт? Войти",
coroutineScope.launch { modifier = Modifier
if (viewModel.signIn(dataStoreManager)) { .clickable {
navController.navigate(Screen.CinemaList.route) isRegistration = false
} }
} .align(Alignment.CenterHorizontally),
}, color = MaterialTheme.colorScheme.onBackground
modifier = Modifier )
.fillMaxWidth() } else {
.padding(8.dp) Button(
) { onClick = {
Text("Вход") coroutine.launch {
if (viewModel.signIn(dataStoreManager)) {
navController.navigate(Screen.CinemaList.route)
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text("Вход")
}
Text(
text = "Нет аккаунта? Зарегистрироваться",
modifier = Modifier
.clickable {
isRegistration = true
}
.align(Alignment.CenterHorizontally),
color = MaterialTheme.colorScheme.onBackground
)
} }
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 val switchColors = SwitchDefaults.colors(
checkedTrackColor = MaterialTheme.colorScheme.secondary, // Change the color of the track when the switch is checked checkedThumbColor = MaterialTheme.colorScheme.primary, // Change the color when the switch is checked
uncheckedThumbColor = MaterialTheme.colorScheme.primary, // Change the color when the switch is unchecked checkedTrackColor = MaterialTheme.colorScheme.secondary, // Change the color of the track when the switch is checked
uncheckedTrackColor = MaterialTheme.colorScheme.onPrimary // Change the color of the track when the switch is unchecked 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)
) )
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.End
) {
Text(
"Темная тема", modifier = Modifier
.align(Alignment.CenterVertically)
.padding(5.dp)
)
Switch( Switch(
checked = isDarkTheme.value, checked = isDarkTheme.value,
onCheckedChange = { onCheckedChange = {
isDarkTheme.value = !isDarkTheme.value isDarkTheme.value = !isDarkTheme.value
coroutine.launch { coroutine.launch {
if (isDarkTheme.value) { if (isDarkTheme.value) {
dataStoreManager.setDarkTheme("Dark") dataStoreManager.setDarkTheme("Dark")
} else { } else {
dataStoreManager.setDarkTheme("Light") dataStoreManager.setDarkTheme("Light")
}
} }
}, }
colors = switchColors },
) colors = switchColors
} )
} }
} }
} }

View File

@ -12,6 +12,7 @@
<string name="Order_title">Мои заказы</string> <string name="Order_title">Мои заказы</string>
<string name="Profile_title">Профиль</string> <string name="Profile_title">Профиль</string>
<string name="Sessions_title">Сеансы</string> <string name="Sessions_title">Сеансы</string>
<string name="Report_title">Отчет</string>
<string name="Session_dateTime">Время</string> <string name="Session_dateTime">Время</string>
<string name="Save_button">Сохранить</string> <string name="Save_button">Сохранить</string>
<string name="Cinema_empty_description">Записи о фильмах отсутствуют</string> <string name="Cinema_empty_description">Записи о фильмах отсутствуют</string>

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "fake-db", "name": "fake-db",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"start": "json-server --watch data.json --host 0.0.0.0 -p 8079" "start": "json-server --watch data.json --middlewares ./reportRouter.js --host 0.0.0.0 -p 8079"
}, },
"dependencies": { "dependencies": {
}, },

62
server/reportRouter.js Normal file
View File

@ -0,0 +1,62 @@
module.exports = (req, res, next) => {
const isReportRequest = req.url.startsWith('/report') && req.method === 'GET';
if (!isReportRequest) {
next();
return;
}
try {
const { startDate, endDate } = req.query;
const { sessions, orders } = require('./data.json');
const start = new Date(startDate);
const end = new Date(endDate);
// Фильтруем сеансы по периоду
const filteredSessions = sessions.filter(session => {
const sessionDate = new Date(session.dateTime.replace(/(\d{2}).(\d{2}).(\d{4}) (\d{2}):(\d{2})/, '$3-$2-$1T$4:$5'));
// обнуление времени с учетом зоны времени
sessionDate.setHours(4, 0, 0, 0);
return sessionDate >= start && sessionDate <= end;
});
// Обрабатываем отфильтрованные сеансы для аналитики
const reportData = filteredSessions.map(session => {
// берем заказ, где сеанс только текущий
const relevantOrders = orders
.map(orderWithOneSession => ({
...orderWithOneSession,
sessions: orderWithOneSession.sessions.filter(orderSession => orderSession.id === session.id &&
orderSession.cinemaId === session.cinemaId && orderSession.dateTime === session.dateTime)
})).filter(order => order.sessions.length > 0);
const { totalTicketsSold, revenue } = relevantOrders.reduce((accumulator, order) => {
const session = order.sessions[0];
const tickets = session.count;
const sessionRevenue = tickets * session.frozenPrice;
return {
totalTicketsSold: accumulator.totalTicketsSold + tickets,
revenue: accumulator.revenue + sessionRevenue
};
}, { totalTicketsSold: 0, revenue: 0 });
return {
cinema_name: relevantOrders[0].sessions[0].cinema.name,
current_ticket_date_time: session.dateTime,
current_ticket_price: session.price,
max_ticket_quantity: session.maxCount,
purchased_tickets: totalTicketsSold,
revenue: revenue
};
});
const sortedReportData = reportData.sort((a, b) => b.revenue - a.revenue);
res.json(sortedReportData);
} catch (error) {
console.error('Error processing report: ', error);
res.status(500).json({ message: 'Internal Server Error' });
}
};