Compare commits

...

8 Commits

Author SHA1 Message Date
zyzf
a1b57f90fd back deployed to server 2023-12-28 15:04:42 +04:00
zyzf
e086b3a04b fixed cart errors 2023-12-28 01:56:21 +04:00
zyzf
ed634ebd21 fixed permissions for statistic screen 2023-12-26 13:51:30 +04:00
Zyzf
c74b39e692 final 2023-12-24 16:20:59 +04:00
zyzf
ebade00c96 moved to postgres db 2023-12-24 01:59:27 +04:00
Zyzf
9a9b066120 need to fix UNIQUE in spring 2023-12-23 23:53:41 +04:00
Zyzf
710b20c3af done with android app?? 2023-12-22 21:13:33 +04:00
zyzf
105378ca52 some done 2023-12-22 17:01:49 +04:00
69 changed files with 1635 additions and 150 deletions

View File

@ -3,20 +3,7 @@
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_7_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-12-20T08:37:35.519292927Z" />
</State>
<State />
</entry>
</value>
</component>

View File

@ -11,7 +11,7 @@ android {
defaultConfig {
applicationId = "com.zyzf.coffeepreorder"
minSdk = 24
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0"

View File

@ -2,7 +2,12 @@ package com.zyzf.coffeepreorder.api
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.zyzf.coffeepreorder.api.model.CoffeeRemote
import com.zyzf.coffeepreorder.api.model.OrderCoffeeCrossRefRemote
import com.zyzf.coffeepreorder.api.model.OrderCoffeeRemote
import com.zyzf.coffeepreorder.api.model.OrderRemote
import com.zyzf.coffeepreorder.api.model.UserRemote
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
@ -77,8 +82,44 @@ interface MyServerService {
@Path("id") id: Int,
): CoffeeRemote
@GET("order/byDate")
suspend fun getOrdersByDate(
@Query("startDate") startDate: String,
@Query("endDate") endDate: String,
): List<OrderCoffeeRemote>
@POST("order/")
suspend fun createOrder(
@Body order: OrderRemote,
): OrderRemote
@GET("order/byUser")
suspend fun getOrdersByUser(
@Query("userId") userId: Int,
@Query("pageNo") page: Int,
@Query("pageSize") limit: Int,
): List<OrderRemote>
@GET("order/coffeesByOrder")
suspend fun getCoffeesByOrder(
@Query("orderId") orderId: Int,
): List<CoffeeRemote>
@GET("order/coffeeCrossRef")
suspend fun getOrderCoffees(): List<OrderCoffeeCrossRefRemote>
@POST("order/coffeeCrossRef")
suspend fun createOrderCoffee(
@Body orderCoffee: OrderCoffeeCrossRefRemote,
)
@DELETE("order/coffeeCrossRef/{id}")
suspend fun deleteOrderCoffee(
@Path("id") id: Int,
)
companion object {
private const val BASE_URL = "http://192.168.0.100:8080/api/"
private const val BASE_URL = "https://api.zyzf.space/api/"
@Volatile
private var INSTANCE: MyServerService? = null

View File

@ -63,7 +63,7 @@ class CoffeeRemoteMediator(
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = coffees.map {
RemoteKeys(
entityId = it.uid,
entityId = it.coffeeId,
type = RemoteKeyType.COFFEE,
prevKey = prevKey,
nextKey = nextKey
@ -83,14 +83,14 @@ class CoffeeRemoteMediator(
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Coffee>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { coffee ->
dbRemoteKeyRepository.getAllRemoteKeys(coffee.uid, RemoteKeyType.COFFEE)
dbRemoteKeyRepository.getAllRemoteKeys(coffee.coffeeId, RemoteKeyType.COFFEE)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Coffee>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { coffee ->
dbRemoteKeyRepository.getAllRemoteKeys(coffee.uid, RemoteKeyType.COFFEE)
dbRemoteKeyRepository.getAllRemoteKeys(coffee.coffeeId, RemoteKeyType.COFFEE)
}
}
@ -98,7 +98,7 @@ class CoffeeRemoteMediator(
state: PagingState<Int, Coffee>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { coffeeUid ->
state.closestItemToPosition(position)?.coffeeId?.let { coffeeUid ->
dbRemoteKeyRepository.getAllRemoteKeys(coffeeUid, RemoteKeyType.COFFEE)
}
}

View File

@ -47,14 +47,14 @@ class RestCoffeeRepository(
service.getCoffee(uid).toCoffee()
override suspend fun insert(coffee: Coffee): Long {
return service.createCoffee(coffee.toCoffeeRemote()).toCoffee().uid.toLong()
return service.createCoffee(coffee.toCoffeeRemote()).toCoffee().coffeeId.toLong()
}
override suspend fun update(coffee: Coffee): Int {
return service.updateCoffee(coffee.uid, coffee.toCoffeeRemote()).toCoffee().uid
return service.updateCoffee(coffee.coffeeId, coffee.toCoffeeRemote()).toCoffee().coffeeId
}
override suspend fun delete(coffee: Coffee) {
service.deleteCoffee(coffee.uid).toCoffee()
service.deleteCoffee(coffee.coffeeId).toCoffee()
}
}

View File

@ -19,7 +19,7 @@ fun CoffeeRemote.toCoffee(): Coffee = Coffee(
)
fun Coffee.toCoffeeRemote(): CoffeeRemote = CoffeeRemote(
uid,
coffeeId,
name,
cost,
ingredients

View File

@ -0,0 +1,23 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import kotlinx.serialization.Serializable
@Serializable
data class OrderCoffeeCrossRefRemote(
val orderId: Int = 0,
val coffeeId: Int = 0,
val count: Int = 0
)
fun OrderCoffeeCrossRefRemote.toOrderCoffeeCrossRef(): OrderCoffeeCrossRef = OrderCoffeeCrossRef(
orderId,
coffeeId,
count
)
fun OrderCoffeeCrossRef.toOrderCoffeeCrossRefRemote(): OrderCoffeeCrossRefRemote = OrderCoffeeCrossRefRemote(
orderId,
coffeeId,
count
)

View File

@ -0,0 +1,27 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.serialization.Serializable
@Serializable
data class OrderCoffeeRemote(
val order: OrderRemote = OrderRemote(),
val coffees: List<Pair<CoffeeRemote, Int>> = listOf()
)
fun OrderCoffeeRemote.toOrderWithCoffees(): OrderWithCoffees {
val convertedOrder = this.order.toOrder()
val convertedCoffees: List<Coffee> = this.coffees.map { it.first.toCoffee() }
val convertedCounts: List<Int> = this.coffees.map { it.second }
return OrderWithCoffees(convertedOrder, order.user.toUser(), convertedCoffees, convertedCounts)
}
fun OrderWithCoffees.toOrderCoffeeRemote(): OrderCoffeeRemote {
val convertedOrder = this.order.toOrderRemote()
var convertedCoffees = ArrayList<Pair<CoffeeRemote, Int>>()
for (i in (0..this.coffees.count()-1)) {
convertedCoffees.add(Pair(this.coffees[i].toCoffeeRemote(), this.counts[i]))
}
return OrderCoffeeRemote(convertedOrder, convertedCoffees)
}

View File

@ -0,0 +1,43 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.user.RestUserRepository
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDataContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.model.User
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
@Serializable
data class OrderRemote(
val id: Int = 0,
val date: String = "",
val user: UserRemote = User.getUser().toUserRemote(),
val sum: Double = 0.0
)
fun OrderRemote.toOrder(): Order = Order(
id,
date,
user.id,
sum
)
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
orderId,
date,
temp(userId),
sum
)
private fun temp(userId: Int): UserRemote {
var user: UserRemote? = null
runBlocking {
user = MyServerService.getInstance().getUser(userId)
}
return user!!
}

View File

@ -23,7 +23,7 @@ fun UserRemote.toUser(): User = User(
)
fun User.toUserRemote(): UserRemote = UserRemote(
uid,
userId,
login,
fio,
phone,

View File

@ -0,0 +1,111 @@
package com.zyzf.coffeepreorder.api.order
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toCoffee
import com.zyzf.coffeepreorder.api.model.toOrder
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.RemoteKeyType
import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineOrderRepository
import com.zyzf.coffeepreorder.database.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.getOrdersByUser(CoffeeApplication.currentUser!!.userId, page, state.config.pageSize).map { it.toOrder() }
val endOfPaginationReached = orders.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
dbOrderRepository.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = orders.map {
RemoteKeys(
entityId = it.orderId,
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.orderId, 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.orderId, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Order>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.orderId?.let { orderUid ->
dbRemoteKeyRepository.getAllRemoteKeys(orderUid, RemoteKeyType.ORDER)
}
}
}
}

View File

@ -0,0 +1,39 @@
package com.zyzf.coffeepreorder.api.order
import android.util.Log
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toOrderCoffeeCrossRef
import com.zyzf.coffeepreorder.api.model.toOrderCoffeeCrossRefRemote
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.repository.OfflineOrderWithCoffeesRepository
import com.zyzf.coffeepreorder.database.repository.OrderWithCoffeesRepository
class RestOrderCoffeesRepository (
private val service: MyServerService,
private val dbOrderCoffeeRepository: OfflineOrderWithCoffeesRepository,
): OrderWithCoffeesRepository {
override suspend fun getAll(): List<OrderCoffeeCrossRef> {
Log.d(RestOrderCoffeesRepository::class.simpleName, "Get OrderCoffees")
val existOrderCoffees = dbOrderCoffeeRepository.getAll().toMutableList()
val serverOrderCoffees = service.getOrderCoffees().map { it.toOrderCoffeeCrossRef() }
// Найти записи для удаления (те, что есть в БД, но отсутствуют на сервере)
val toDelete = existOrderCoffees.filterNot { serverOrderCoffees.contains(it) }
toDelete.forEach { dbOrderCoffeeRepository.delete(it) }
// Найти новые записи для добавления (те, что есть на сервере, но отсутствуют в БД)
val toAdd = serverOrderCoffees.filterNot { existOrderCoffees.contains(it) }
toAdd.forEach { dbOrderCoffeeRepository.insert(it) }
// Вернуть обновленный список записей из БД
return dbOrderCoffeeRepository.getAll()
}
override suspend fun insert(orderCoffee: OrderCoffeeCrossRef) {
service.createOrderCoffee(orderCoffee.toOrderCoffeeCrossRefRemote())
}
override suspend fun delete(orderCoffee: OrderCoffeeCrossRef) {
service.deleteOrderCoffee(orderCoffee.orderId)
}
}

View File

@ -0,0 +1,62 @@
package com.zyzf.coffeepreorder.api.order
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toCoffee
import com.zyzf.coffeepreorder.api.model.toOrder
import com.zyzf.coffeepreorder.api.model.toOrderRemote
import com.zyzf.coffeepreorder.api.model.toOrderWithCoffees
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.repository.OfflineOrderRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flowOf
class RestOrderRepository(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : OrderRepository {
override suspend fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>> {
return flowOf(service.getCoffeesByOrder(orderId).map { it.toCoffee() })
}
override suspend fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>> {
return flowOf(service.getOrdersByDate(startDate, endDate).map { it.toOrderWithCoffees() })
}
@OptIn(ExperimentalPagingApi::class)
override fun getOrdersByUser(id: Int): Flow<PagingData<Order>> {
Log.d(RestOrderRepository::class.simpleName, "Get Orders by User")
val pagingSourceFactory = { dbOrderRepository.getOrdersByUserPagingSource(id) }
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = OrderRemoteMediator(
service,
dbOrderRepository,
dbRemoteKeyRepository,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun insert(order: Order): Long {
return service.createOrder(order.toOrderRemote()).toOrder().orderId.toLong()
}
}

View File

@ -44,21 +44,21 @@ class RestUserRepository(
}
override suspend fun getByUid(uid: Int): User =
service.getUser(uid).toUser()!!
service.getUser(uid).toUser()
override suspend fun tryLogin(login: String, password: String): User? =
override suspend fun tryLogin(login: String, password: String): User =
service.tryLogin(login, password).toUser()
override suspend fun insert(user: User): Long {
return service.createUser(user.toUserRemote()).toUser()?.uid?.toLong()!!
return service.createUser(user.toUserRemote()).toUser().userId.toLong()
}
override suspend fun update(user: User): Int {
return service.updateUser(user.uid, user.toUserRemote()).toUser()?.uid!!
return service.updateUser(user.userId, user.toUserRemote()).toUser().userId
}
override suspend fun delete(user: User) {
service.deleteUser(user.uid).toUser()
service.deleteUser(user.userId).toUser()
}
}

View File

@ -63,7 +63,7 @@ class UserRemoteMediator(
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = users.map {
RemoteKeys(
entityId = it.uid,
entityId = it.userId,
type = RemoteKeyType.USER,
prevKey = prevKey,
nextKey = nextKey
@ -83,14 +83,14 @@ class UserRemoteMediator(
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, User>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { user ->
dbRemoteKeyRepository.getAllRemoteKeys(user.uid, RemoteKeyType.USER)
dbRemoteKeyRepository.getAllRemoteKeys(user.userId, RemoteKeyType.USER)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, User>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { user ->
dbRemoteKeyRepository.getAllRemoteKeys(user.uid, RemoteKeyType.USER)
dbRemoteKeyRepository.getAllRemoteKeys(user.userId, RemoteKeyType.USER)
}
}
@ -98,7 +98,7 @@ class UserRemoteMediator(
state: PagingState<Int, User>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { userUid ->
state.closestItemToPosition(position)?.userId?.let { userUid ->
dbRemoteKeyRepository.getAllRemoteKeys(userUid, RemoteKeyType.USER)
}
}

View File

@ -3,10 +3,14 @@ package com.zyzf.coffeepreorder.database
import android.content.Context
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.coffee.RestCoffeeRepository
import com.zyzf.coffeepreorder.api.order.RestOrderCoffeesRepository
import com.zyzf.coffeepreorder.api.order.RestOrderRepository
import com.zyzf.coffeepreorder.api.user.RestUserRepository
import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineOrderRepository
import com.zyzf.coffeepreorder.database.repository.OfflineOrderWithCoffeesRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository
@ -14,6 +18,8 @@ interface AppContainer {
val cartRepository: CartRepository
val coffeeRestRepository: RestCoffeeRepository
val userRestRepository: RestUserRepository
val orderRestRepository: RestOrderRepository
val orderCoffeesRestRepository: RestOrderCoffeesRepository
companion object {
const val LIMIT = 10
@ -27,6 +33,12 @@ class AppDataContainer(private val context: Context) : AppContainer {
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val orderRepository: OfflineOrderRepository by lazy {
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
}
private val orderCoffeesRepository: OfflineOrderWithCoffeesRepository by lazy {
OfflineOrderWithCoffeesRepository(AppDatabase.getInstance(context).orderWithCoffeesDao())
}
override val cartRepository: CartRepository by lazy {
OfflineCartRepository(AppDatabase.getInstance(context).cartDao())
}
@ -49,4 +61,18 @@ class AppDataContainer(private val context: Context) : AppContainer {
AppDatabase.getInstance(context)
)
}
override val orderRestRepository: RestOrderRepository by lazy {
RestOrderRepository(
MyServerService.getInstance(),
orderRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val orderCoffeesRestRepository: RestOrderCoffeesRepository by lazy {
RestOrderCoffeesRepository(
MyServerService.getInstance(),
orderCoffeesRepository
)
}
}

View File

@ -6,18 +6,24 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import com.zyzf.coffeepreorder.database.dao.CartDao
import com.zyzf.coffeepreorder.database.dao.CoffeeDao
import com.zyzf.coffeepreorder.database.dao.OrderDao
import com.zyzf.coffeepreorder.database.dao.OrderWithCoffees
import com.zyzf.coffeepreorder.database.dao.RemoteKeysDao
import com.zyzf.coffeepreorder.database.dao.UserDao
import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.model.User
@Database(entities = [User::class, Coffee::class, Cart::class, RemoteKeys::class], version = 1, exportSchema = false)
@Database(entities = [User::class, Coffee::class, Cart::class, Order::class, OrderCoffeeCrossRef::class, RemoteKeys::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun coffeeDao(): CoffeeDao
abstract fun cartDao(): CartDao
abstract fun orderDao(): OrderDao
abstract fun orderWithCoffeesDao(): OrderWithCoffees
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {

View File

@ -14,14 +14,17 @@ interface CartDao {
@Query("select * from cart")
suspend fun getAll(): Cart
@Query("select coffee.uid, coffee.name, coffee.cost, coffee.ingredients from cart join coffee on coffee.uid = cart.coffee_id and cart.count > 0 collate nocase")
@Query("select coffee.* from cart join coffee on coffee.coffee_id = cart.coffee_id and cart.count > 0 collate nocase")
fun getAllInCart(): PagingSource<Int, Coffee>
@Query("select sum(coffee.cost * cart.count) from cart JOIN coffee on coffee.uid = cart.coffee_id and cart.count > 0")
@Query("select coffee.* from cart join coffee on coffee.coffee_id = cart.coffee_id and cart.count > 0 collate nocase")
suspend fun getAllInCartList(): List<Coffee>
@Query("select sum(coffee.cost * cart.count) from cart JOIN coffee on coffee.coffee_id = cart.coffee_id and cart.count > 0")
fun getSumInCart(): Double
@Query("select cart.count from cart JOIN coffee on coffee.uid = cart.coffee_id where coffee.uid = :coffeeId")
fun getCountForCoffee(coffeeId: Int): Double
@Query("select cart.count from cart JOIN coffee on coffee.coffee_id = cart.coffee_id where coffee.coffee_id = :coffeeId")
fun getCountForCoffee(coffeeId: Int): Int
@Insert
suspend fun insert(cart: Cart)

View File

@ -13,7 +13,7 @@ interface CoffeeDao {
@Query("select * from coffee order by name collate nocase asc")
fun getAllCoffees(): PagingSource<Int, Coffee>
@Query("select coffee.uid, name, cost, ingredients from coffee where coffee.uid = :uid")
@Query("select coffee.coffee_id, name, cost, ingredients from coffee where coffee.coffee_id = :uid")
suspend fun getByUid(uid: Int): Coffee?
@Insert

View File

@ -0,0 +1,56 @@
package com.zyzf.coffeepreorder.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Query("select * from `order` order by order_id collate nocase asc")
fun getAll(): PagingSource<Int, Order>
@Query("select * from `order` where `order`.order_id = :uid")
suspend fun getByUid(uid: Int): Order?
@Query("""
SELECT `order`.* FROM `order`
WHERE user_id = :userId
""")
fun getOrdersByUser(userId: Int): PagingSource<Int, Order>
@Query("""
SELECT coffee.* FROM coffee
INNER JOIN ordercoffeecrossref ON coffee.coffee_id = ordercoffeecrossref.coffee_id
WHERE ordercoffeecrossref.order_id = :orderId
""")
fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>>
@Query("""
SELECT `order`.* FROM `order`
INNER JOIN ordercoffeecrossref ON `order`.order_id = ordercoffeecrossref.order_id
WHERE date >= :startDate and date <= :endDate
""")
fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>>
@Insert
fun insert(order: Order): Long
@Insert
suspend fun insert(vararg order: Order)
@Update
fun update(order: Order): Int
@Delete
suspend fun delete(order: Order)
@Query("delete from `order`")
suspend fun deleteAll()
}

View File

@ -0,0 +1,17 @@
package com.zyzf.coffeepreorder.database.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
@Dao
interface OrderWithCoffees {
@Query("select * from ordercoffeecrossref")
fun getAll(): List<OrderCoffeeCrossRef>
@Insert
suspend fun insert(vararg orderCoffee: OrderCoffeeCrossRef)
@Delete
suspend fun delete(orderCoffee: OrderCoffeeCrossRef)
}

View File

@ -16,7 +16,7 @@ interface UserDao {
@Query("select * from user where login = :login and password = :password")
suspend fun tryLogin(login: String, password: String): User?
@Query("select * from user where uid = :uid")
@Query("select * from user where user_id = :uid")
suspend fun getByUid(uid: Int): User?
@Insert

View File

@ -4,23 +4,26 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "cart", foreignKeys = [
ForeignKey(
entity = Cart::class,
parentColumns = ["uid"],
childColumns = ["coffee_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
])
//@Entity(tableName = "cart", foreignKeys = [
// ForeignKey(
// entity = Coffee::class,
// parentColumns = arrayOf("coffee_id"),
// childColumns = arrayOf("coffee_id"),
// onDelete = ForeignKey.SET_DEFAULT,
// onUpdate = ForeignKey.CASCADE
// )
//])
@Entity(tableName = "cart")
data class Cart(
@PrimaryKey(autoGenerate = true)
val uid: Int = 0,
@ColumnInfo(name = "cart_id")
val cartId: Int = 0,
@ColumnInfo(name = "coffee_id", index = true)
val coffeeId: Int,
@ColumnInfo(name = "count", index = true)
@ColumnInfo(name = "count")
val count: Int = 0
) {
@Ignore
@ -33,13 +36,13 @@ data class Cart(
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Cart
if (uid != other.uid) return false
if (cartId != other.cartId) return false
if (coffeeId != other.coffeeId) return false
return count == other.count
}
override fun hashCode(): Int {
var result = uid
var result = cartId
result = 31 * result + coffeeId.hashCode()
result = 31 * result + count.hashCode()
return result

View File

@ -8,7 +8,8 @@ import androidx.room.PrimaryKey
@Entity(tableName = "coffee")
data class Coffee(
@PrimaryKey(autoGenerate = true)
val uid: Int = 0,
@ColumnInfo(name = "coffee_id")
val coffeeId: Int = 0,
@ColumnInfo(name = "name")
var name: String,
@ColumnInfo(name = "cost")
@ -38,14 +39,14 @@ data class Coffee(
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Coffee
if (uid != other.uid) return false
if (coffeeId != other.coffeeId) return false
if (name != other.name) return false
if (cost != other.cost) return false
return ingredients == other.ingredients
}
override fun hashCode(): Int {
var result = uid
var result = coffeeId
result = 31 * result + name.hashCode()
result = 31 * result + cost.hashCode()
result = 31 * result + ingredients.hashCode()

View File

@ -0,0 +1,65 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(
tableName = "order",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = arrayOf("user_id"),
childColumns = arrayOf("user_id"),
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)]
)
data class Order(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "order_id")
val orderId: Int = 0,
@ColumnInfo(name = "date")
var date: String,
@ColumnInfo(name = "user_id", index = true)
val userId: Int,
@ColumnInfo(name = "sum")
val sum: Double
) {
@Ignore
constructor(
date: String,
userId: Int,
sum: Double
) : this(0, date, userId, sum)
companion object {
fun getOrder(index: Int = 0): Order {
return Order(
index,
"",
0,
0.0
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Order
if (orderId != other.orderId) return false
if (date != other.date) return false
if (userId != other.userId) return false
return sum == other.sum
}
override fun hashCode(): Int {
var result = orderId
result = 31 * result + date.hashCode()
result = 31 * result + userId.hashCode()
result = 31 * result + sum.hashCode()
return result
}
}

View File

@ -0,0 +1,14 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
@Entity(primaryKeys = ["order_id", "coffee_id"])
class OrderCoffeeCrossRef (
@ColumnInfo(name = "order_id")
val orderId: Int,
@ColumnInfo(name = "coffee_id")
val coffeeId: Int,
@ColumnInfo(name = "count")
val count: Int
)

View File

@ -0,0 +1,27 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class OrderWithCoffees(
@Embedded val order: Order,
@Relation(
parentColumn = "order_id",
entityColumn = "user_id"
)
val user: User,
@Relation(
parentColumn = "order_id",
entityColumn = "coffee_id",
associateBy = Junction(OrderCoffeeCrossRef::class)
)
val coffees: List<Coffee>,
@Relation(
parentColumn = "order_id",
entityColumn = "coffee_id",
entity = OrderCoffeeCrossRef::class,
projection = ["count"]
)
val counts: List<Int>
)

View File

@ -7,7 +7,8 @@ import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
COFFEE(Coffee::class.simpleName ?: "Coffee"),
USER(User::class.simpleName ?: "User");
USER(User::class.simpleName ?: "User"),
ORDER(Order::class.simpleName ?: "Order");
@TypeConverter
fun toRemoteKeyType(value: String) = entries.first { it.type == value }

View File

@ -8,7 +8,8 @@ import androidx.room.PrimaryKey
@Entity(tableName = "user")
data class User(
@PrimaryKey(autoGenerate = true)
val uid: Int = 0,
@ColumnInfo(name = "user_id")
val userId: Int = 0,
@ColumnInfo(name = "login")
var login: String,
@ColumnInfo(name = "fio")
@ -46,7 +47,7 @@ data class User(
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as User
if (uid != other.uid) return false
if (userId != other.userId) return false
if (login != other.login) return false
if (fio != other.fio) return false
if (phone != other.phone) return false
@ -55,7 +56,7 @@ data class User(
}
override fun hashCode(): Int {
var result = uid
var result = userId
result = 31 * result + login.hashCode()
result = 31 * result + fio.hashCode()
result = 31 * result + phone.hashCode()

View File

@ -9,10 +9,11 @@ interface CartRepository {
suspend fun getAll(): Cart
suspend fun insert(cart: Cart)
fun getAllInCart(): Flow<PagingData<Coffee>>
suspend fun getAllInCartList(): List<Coffee>
fun getSumInCart(): Double
suspend fun insertCoffee(coffeeId: Int, count: Int)
suspend fun deleteCoffee(coffeeId: Int, count: Int)
fun getCountForCoffee(coffeeId: Int): Double
fun getCountForCoffee(coffeeId: Int): Int
suspend fun update(cart: Cart)
suspend fun deleteAll()
}

View File

@ -17,12 +17,14 @@ class OfflineCartRepository(private val cartDao: CartDao) : CartRepository {
),
pagingSourceFactory = cartDao::getAllInCart
).flow
override suspend fun getAllInCartList(): List<Coffee> = cartDao.getAllInCartList()
override fun getSumInCart(): Double = cartDao.getSumInCart()
override suspend fun getAll(): Cart = cartDao.getAll()
override suspend fun insert(cart: Cart) = cartDao.insert(cart)
override suspend fun insertCoffee(coffeeId: Int, count: Int) = cartDao.insertCoffee(coffeeId, count)
override suspend fun deleteCoffee(coffeeId: Int, count: Int) = cartDao.deleteCoffee(coffeeId, count)
override fun getCountForCoffee(coffeeId: Int): Double = cartDao.getCountForCoffee(coffeeId)
override fun getCountForCoffee(coffeeId: Int): Int = cartDao.getCountForCoffee(coffeeId)
override suspend fun update(cart: Cart) = cartDao.update(cart)
override suspend fun deleteAll() = cartDao.deleteAll()
}

View File

@ -0,0 +1,33 @@
package com.zyzf.coffeepreorder.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.dao.OrderDao
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import kotlinx.coroutines.flow.Flow
class OfflineOrderRepository(private val orderDao: OrderDao) : OrderRepository {
override fun getOrdersByUser(id: Int): Flow<PagingData<Order>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { orderDao.getOrdersByUser(id) }
).flow
override suspend fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>> = orderDao.getCoffeesByOrder(orderId)
override suspend fun insert(order: Order) = orderDao.insert(order)
override suspend fun getOrdersByDate(
startDate: String,
endDate: String
): Flow<List<OrderWithCoffees>> = orderDao.getOrdersByDate(startDate, endDate)
fun getOrdersByUserPagingSource(id: Int): PagingSource<Int, Order> = orderDao.getOrdersByUser(id)
suspend fun deleteAll() = orderDao.deleteAll()
suspend fun insertOrders(orders: List<Order>) =
orderDao.insert(*orders.toTypedArray())
}

View File

@ -0,0 +1,12 @@
package com.zyzf.coffeepreorder.database.repository
import com.zyzf.coffeepreorder.database.dao.OrderWithCoffees
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
class OfflineOrderWithCoffeesRepository(private val orderCoffeesDAO: OrderWithCoffees) : OrderWithCoffeesRepository {
override suspend fun getAll() = orderCoffeesDAO.getAll()
override suspend fun insert(orderCoffee: OrderCoffeeCrossRef) = orderCoffeesDAO.insert(orderCoffee)
override suspend fun delete(orderCoffee: OrderCoffeeCrossRef) = orderCoffeesDAO.delete(orderCoffee)
suspend fun insertAll(orderBouquet: List<OrderCoffeeCrossRef>) =
orderCoffeesDAO.insert(*orderBouquet.toTypedArray())
}

View File

@ -0,0 +1,14 @@
package com.zyzf.coffeepreorder.database.repository
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.coroutines.flow.Flow
interface OrderRepository {
suspend fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>>
fun getOrdersByUser(id: Int): Flow<PagingData<Order>>
suspend fun insert(order: Order): Long
suspend fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>>
}

View File

@ -0,0 +1,9 @@
package com.zyzf.coffeepreorder.database.repository
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
interface OrderWithCoffeesRepository {
suspend fun getAll(): List<OrderCoffeeCrossRef>
suspend fun insert(orderCoffee: OrderCoffeeCrossRef)
suspend fun delete(orderCoffee: OrderCoffeeCrossRef)
}

View File

@ -8,8 +8,10 @@ import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.ui.cart.CartViewModel
import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel
import com.zyzf.coffeepreorder.ui.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.order.OrderViewModel
import com.zyzf.coffeepreorder.ui.profile.ProfileViewModel
import com.zyzf.coffeepreorder.ui.register.RegisterViewModel
import com.zyzf.coffeepreorder.ui.statistic.OrderStatisticViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
@ -26,7 +28,16 @@ object AppViewModelProvider {
RegisterViewModel(coffeeApplication().container.userRestRepository)
}
initializer {
ProfileViewModel(coffeeApplication().container.userRestRepository)
ProfileViewModel(coffeeApplication().container.userRestRepository,
coffeeApplication().container.cartRepository)
}
initializer {
OrderStatisticViewModel(coffeeApplication().container.orderRestRepository)
}
initializer {
OrderViewModel(coffeeApplication().container.orderRestRepository,
coffeeApplication().container.orderCoffeesRestRepository,
coffeeApplication().container.cartRepository)
}
}
}

View File

@ -104,7 +104,7 @@ fun Cart(
)
CartList(
coffeeList = coffeeListUiState,
onDeleteFromCartClick = {currentCoffee: Coffee ->
onDeleteFromCartClick = { currentCoffee: Coffee ->
coffee.value = currentCoffee
openDialog.value = true
}
@ -126,7 +126,7 @@ fun Cart(
private fun CartList(
coffeeList: LazyPagingItems<Coffee>,
onDeleteFromCartClick: (coffee: Coffee) -> Unit,
getCoffeeCount: (coffeeId: Int) -> Double
getCoffeeCount: (Int) -> Int
) {
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally) {
@ -151,7 +151,7 @@ private fun CartList(
private fun CartListItem (
coffee: Coffee,
onDeleteFromCartClick: (coffee: Coffee) -> Unit,
getCoffeeCount: (coffeeId: Int) -> Double
getCoffeeCount: (coffeeId: Int) -> Int
) {
Row(modifier = Modifier
.fillMaxWidth()
@ -159,7 +159,7 @@ private fun CartListItem (
.padding(bottom = 10.dp, top = 10.dp),
horizontalArrangement = Arrangement.SpaceAround) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png")
model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.coffeeId +".png")
.crossfade(true).build(),
error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img),
@ -173,7 +173,7 @@ private fun CartListItem (
Modifier
.weight(2f)
.padding(start = 20.dp)) {
val coffeeCount: Double = getCoffeeCount(coffee.uid)
val coffeeCount: Int = getCoffeeCount(coffee.coffeeId)
val currentCoffeeName: String = if (coffeeCount > 1) {
coffee.name + " x" + coffeeCount.toString()
} else {

View File

@ -11,11 +11,11 @@ class CartViewModel(
) : ViewModel() {
val coffeeListUiState: Flow<PagingData<Coffee>> = cartRepository.getAllInCart()
fun getCountForCoffee(coffeeId: Int): Double {
fun getCountForCoffee(coffeeId: Int): Int {
return cartRepository.getCountForCoffee(coffeeId)
}
suspend fun deleteCoffeeFromCart(coffee: Coffee) {
cartRepository.deleteCoffee(coffee.uid, 1)
cartRepository.deleteCoffee(coffee.coffeeId, 1)
}
}

View File

@ -180,7 +180,7 @@ fun CoffeeList(
imageUri = imageUri,
beforeOpen = {coffeeUid: Int ->
if (coffeeUid != 0 && !isImageChanged.value) {
imageUri = Uri.parse("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.uid +".png")
imageUri = Uri.parse("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.coffeeId +".png")
}
}
)
@ -199,7 +199,7 @@ private fun AddEditModalBottomSheet(
imageUri: Any?,
beforeOpen: (coffeeUid: Int) -> Unit
) {
beforeOpen(coffee.value.uid)
beforeOpen(coffee.value.coffeeId)
var name: String by remember { mutableStateOf("")}
var cost: Double by remember { mutableDoubleStateOf(0.0) }
var ingredients: String by remember { mutableStateOf("")}
@ -271,16 +271,16 @@ private fun AddEditModalBottomSheet(
.build(),
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
if (coffee.value.uid == 0) {
if (coffee.value.coffeeId == 0) {
Button(onClick = {
onAddClick(Coffee(coffee.value.uid, name, cost, ingredients), context)
onAddClick(Coffee(coffee.value.coffeeId, name, cost, ingredients), context)
openDialog.value = false
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
Text("Добавить")
}
} else {
Button(onClick = {
onEditClick(Coffee(coffee.value.uid, name, cost, ingredients), context)
onEditClick(Coffee(coffee.value.coffeeId, name, cost, ingredients), context)
openDialog.value = false
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
Text("Изменить")
@ -339,7 +339,7 @@ private fun CoffeeListItem(
AsyncImage(
model = ImageRequest
.Builder(context = LocalContext.current)
.data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png")
.data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.coffeeId +".png")
.crossfade(true).build(),
error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img),
@ -363,7 +363,7 @@ private fun CoffeeListItem(
.padding(top = 5.dp)) {
Button(
onClick = {
onAddToCartClick(coffee.uid)
onAddToCartClick(coffee.coffeeId)
},
shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f)

View File

@ -55,6 +55,7 @@ class CoffeeListViewModel(
suspend fun editCoffee(coffee: Coffee, imageUri: Any?, context: Context) {
val editedCoffee: Int = coffeeRepository.update(coffee)
if (imageUri !is Uri) return
if (imageUri.host == "zyzf.space") return
val inputStream = context.contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream)

View File

@ -1,6 +1,5 @@
package com.zyzf.coffeepreorder.ui.login
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -16,7 +15,6 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -33,7 +31,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
@ -45,8 +42,6 @@ import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -142,17 +137,4 @@ fun Login(
Text(text = "Зарегистрироваться")
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun LoginPreview() {
CoffeePreorderTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Login(null)
}
}
}

View File

@ -9,7 +9,7 @@ class LoginViewModel(
) : ViewModel() {
suspend fun tryLogin(login: String, password: String): User? {
val user: User? = userRepository.tryLogin(login, password)
return if (user?.uid == 0) {
return if (user?.userId == 0) {
null
} else {
user

View File

@ -1,5 +1,7 @@
package com.zyzf.coffeepreorder.ui.navigation
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@ -26,6 +28,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.ui.cart.Cart
import com.zyzf.coffeepreorder.ui.coffee.CoffeeList
@ -33,6 +36,7 @@ import com.zyzf.coffeepreorder.ui.login.Login
import com.zyzf.coffeepreorder.ui.order.Order
import com.zyzf.coffeepreorder.ui.profile.Profile
import com.zyzf.coffeepreorder.ui.register.Register
import com.zyzf.coffeepreorder.ui.statistic.OrderStatistic
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -74,6 +78,11 @@ fun Navbar(
) {
NavigationBar(modifier) {
Screen.bottomBarItems.forEach { screen ->
if (CoffeeApplication.currentUser != null &&
CoffeeApplication.currentUser!!.role != "admin" &&
screen.route == "order-statistic") {
return@forEach
}
NavigationBarItem(
icon = { Icon(screen.icon, contentDescription = null) },
label = { Text(stringResource(screen.resourceId)) },
@ -92,6 +101,7 @@ fun Navbar(
}
}
@Composable
fun Navhost(
navController: NavHostController,
@ -109,6 +119,8 @@ fun Navhost(
composable(Screen.Profile.route) { Profile(navController) }
composable(Screen.Cart.route) { Cart(navController) }
composable(Screen.Order.route) { Order(navController) }
composable(Screen.OrderStatistic.route) { OrderStatistic() }
}
}

View File

@ -3,11 +3,13 @@ package com.zyzf.coffeepreorder.ui.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.ui.graphics.vector.ImageVector
import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppContainer
enum class Screen(
val route: String,
@ -32,13 +34,17 @@ enum class Screen(
),
Order(
"order", R.string.coffee_order, Icons.Filled.ShoppingCart
),
OrderStatistic(
"order-statistic", R.string.coffee_order_statistic, Icons.Filled.DateRange
);
companion object {
val bottomBarItems = listOf(
CoffeeList,
Profile,
Cart
Cart,
OrderStatistic
)
fun getItem(route: String): Screen? {

View File

@ -1,6 +1,5 @@
package com.zyzf.coffeepreorder.ui.order
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -24,7 +23,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TimePicker
import androidx.compose.material3.rememberDatePickerState
@ -33,18 +31,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Calendar
@ -53,18 +51,21 @@ import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Order(navController: NavController?) {
fun Order(
navController: NavController?,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val openDatePicker = remember { mutableStateOf(false) }
val openTimePicker = remember { mutableStateOf(false) }
val calendar = Calendar.getInstance()
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = calendar.timeInMillis)
val timePickerState = rememberTimePickerState(is24Hour = true, initialHour = calendar.get(Calendar.HOUR_OF_DAY), initialMinute = calendar.get(Calendar.MINUTE))
val formatter = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT)
val context = LocalContext.current
val sum = remember { mutableStateOf(0.0) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
sum.value = AppDatabase.getInstance(context).cartDao().getSumInCart()
sum.value = viewModel.getSumInCart()
}
}
Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) {
@ -107,7 +108,12 @@ fun Order(navController: NavController?) {
}
Box(modifier = Modifier.fillMaxSize()) {
ExtendedFloatingActionButton(
onClick = { navController?.navigate(Screen.CoffeeList.route) },
onClick = {
coroutineScope.launch {
viewModel.createNewOrder(datePickerState.selectedDateMillis)
}
navController?.navigate(Screen.Cart.route)
},
icon = { Icon(Icons.Filled.Add, "Оформить заказ") },
text = { Text(text = "Оформить заказ") },
modifier = Modifier
@ -169,17 +175,4 @@ fun Order(navController: NavController?) {
}
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun OrderPreview() {
CoffeePreorderTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Order(null)
}
}
}

View File

@ -0,0 +1,36 @@
package com.zyzf.coffeepreorder.ui.order
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import com.zyzf.coffeepreorder.database.repository.OrderWithCoffeesRepository
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class OrderViewModel(
private val orderRepository: OrderRepository,
private val orderWithCoffeesRepository: OrderWithCoffeesRepository,
private val cartRepository: CartRepository
) : ViewModel() {
fun getSumInCart(): Double {
return cartRepository.getSumInCart()
}
suspend fun createNewOrder(date: Long?) {
val coffees: List<Coffee> = cartRepository.getAllInCartList()
if (coffees.isEmpty() || date == null) {
return
}
val cartSum: Double = cartRepository.getSumInCart()
val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT)
val currentOrderId: Long = orderRepository.insert(Order(formatter.format(Date(date)), CoffeeApplication.currentUser!!.userId, cartSum))
coffees.forEach { coffee: Coffee ->
orderWithCoffeesRepository.insert(OrderCoffeeCrossRef(currentOrderId.toInt(), coffee.coffeeId, cartRepository.getCountForCoffee(coffee.coffeeId)))
}
}
}

View File

@ -47,10 +47,8 @@ import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -204,7 +202,7 @@ fun Profile(
onClick = {
coroutineScope.launch {
val user: User = viewModel.changeUser(user.password, userOldPsswd.value, userNewPsswd.value, userNewPsswd.value,
user.uid, userLogin, userFIO, userPhone, user.role)
user.userId, userLogin, userFIO, userPhone, user.role)
CoffeeApplication.currentUser = user
}
openDialogEdit.value = false
@ -241,8 +239,8 @@ fun Profile(
confirmButton = {
TextButton(
onClick = {
GlobalScope.launch (Dispatchers.Main) {
CoffeeApplication.currentUser = null
coroutineScope.launch {
viewModel.logout()
}
openDialogExit.value = false
navController?.navigate(Screen.Login.route)
@ -279,9 +277,8 @@ fun Profile(
confirmButton = {
TextButton(
onClick = {
GlobalScope.launch (Dispatchers.Main) {
CoffeeApplication.currentUser = null
AppDatabase.getInstance(context).userDao().delete(user)
coroutineScope.launch {
viewModel.deleteAccount(user)
}
openDialogDelete.value = false
navController?.navigate(Screen.Login.route)

View File

@ -2,12 +2,13 @@ package com.zyzf.coffeepreorder.ui.profile
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.UserRepository
class ProfileViewModel(
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val cartRepository: CartRepository
) : ViewModel() {
suspend fun changeUser(currentUserPassw: String,
userOldPsswd: String,
@ -25,6 +26,16 @@ class ProfileViewModel(
val userUid: Int? = userRepository.update(User(
userUid, userLogin, userFIO, userPhone, currentUserPassw, userRole))
}
return userRepository.getByUid(userUid!!)!!
return userRepository.getByUid(userUid)!!
}
suspend fun logout() {
CoffeeApplication.currentUser = null
cartRepository.deleteAll()
}
suspend fun deleteAccount(user: User) {
logout()
userRepository.delete(user)
}
}

View File

@ -44,7 +44,6 @@ import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

View File

@ -0,0 +1,281 @@
package com.zyzf.coffeepreorder.ui.statistic
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.Date
import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OrderStatistic(
viewModel: OrderStatisticViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val openDatePicker = remember { mutableStateOf(false) }
val calendar = Calendar.getInstance()
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = calendar.timeInMillis)
val coroutineScope = rememberCoroutineScope()
val orderListUiState = viewModel.orderListUiState.collectAsState(initial = listOf())
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
val startDate = remember { mutableStateOf(LocalDate.now().minusDays(1).format(formatter))}
val endDate = remember { mutableStateOf(LocalDate.now().format(formatter))}
val isStartDate = remember { mutableStateOf(true)}
Scaffold(
topBar = {},
floatingActionButton = {
if (CoffeeApplication.currentUser?.role == "admin") {
FloatingActionButton(
onClick = {
coroutineScope.launch {
viewModel.getOrdersByDate(startDate.value, endDate.value, coroutineScope)
}
},
Modifier
.padding(all = 20.dp)
) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Perform",
modifier = Modifier.size(20.dp)
)
}
}
}
) { innerPadding ->
Column (modifier = Modifier
.padding(innerPadding).fillMaxSize()) {
StartEndDateBlock(
startDate = startDate,
endDate = endDate,
onStartDateClick = {
isStartDate.value = true
openDatePicker.value = true
},
onEndDateClick = {
isStartDate.value = false
openDatePicker.value = true
}
)
TotalBlock(orderList = orderListUiState.value)
OrderList(
orderList = orderListUiState
)
}
DateDialog(
startDate = startDate,
endDate = endDate,
isStartDate = isStartDate,
openDatePicker = openDatePicker,
datePickerState = datePickerState
)
}
}
@Composable
private fun OrderList(
orderList: State<List<OrderWithCoffees>>,
) {
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
items(items = orderList.value) {order ->
OrderListItem(
order = order
)
}
}
}
@Composable
private fun StartEndDateBlock(
startDate: MutableState<String>,
endDate: MutableState<String>,
onStartDateClick: () -> Unit,
onEndDateClick: () -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 140.dp)
.padding(bottom = 10.dp, top = 10.dp)) {
Row(horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically) {
Button(onClick = {onStartDateClick()},
shape = CircleShape,
modifier = Modifier.width(100.dp).padding(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(text = "От")
}
Text(text = startDate.value, modifier = Modifier.padding(10.dp))
Button(onClick = {onEndDateClick()},
shape = CircleShape,
modifier = Modifier.width(100.dp).padding(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(text = "До")
}
Text(text = endDate.value, modifier = Modifier.padding(10.dp))
}
}
}
@Composable
private fun TotalBlock(
orderList: List<OrderWithCoffees>
) {
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 140.dp)
.padding(bottom = 10.dp, top = 10.dp)) {
Row(horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier.width(145.dp)) {
Text(text = "Итого: ", modifier = Modifier.padding(5.dp))
Text(text = orderList.sumOf { it.order.sum }.toString(), modifier = Modifier.padding(5.dp))
}
Row(modifier = Modifier.width(145.dp)) {
Text(text = "Количество: ", modifier = Modifier.padding(5.dp))
Text(text = orderList.count().toString(), modifier = Modifier.padding(5.dp))
}
Row(modifier = Modifier.width(145.dp)) {
Text(text = "Среднее: ", modifier = Modifier.padding(5.dp))
Text(text = (orderList.sumOf { it.order.sum } / orderList.count()).toString(), modifier = Modifier.padding(5.dp))
}
}
}
}
@Composable
private fun OrderListItem(
order: OrderWithCoffees
) {
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)
.padding(bottom = 10.dp, top = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = order.user.fio, fontSize = 25.sp)
Text(text = order.user.phone, fontSize = 25.sp)
Text(text = String.format("%.2f", order.order.sum), fontSize = 30.sp)
Row() {
for (i in (0..order.coffees.count()-1)) {
if (order.counts[i] > 1) {
Text(text = order.coffees[i].name + " x" + order.counts[i] + "(" + order.coffees[i].cost * order.counts[i] + ")", fontSize = 15.sp)
} else {
Text(text = order.coffees[i].name + "(" + order.coffees[i].cost + ")", fontSize = 15.sp)
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DateDialog(
startDate: MutableState<String>,
endDate: MutableState<String>,
isStartDate: MutableState<Boolean>,
openDatePicker: MutableState<Boolean>,
datePickerState: DatePickerState
) {
if (openDatePicker.value) {
Dialog(onDismissRequest = { openDatePicker.value = false },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true, usePlatformDefaultWidth = false)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.7f)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.End) {
Button(
onClick = {
if (datePickerState.selectedDateMillis != null) {
val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT)
if (isStartDate.value) {
startDate.value = formatter.format(Date(datePickerState.selectedDateMillis!!))
} else {
endDate.value = formatter.format(Date(datePickerState.selectedDateMillis!!))
}
}
openDatePicker.value = false
},
modifier = Modifier.padding(10.dp),
) {
Text("Ок")
}
DatePicker(
state = datePickerState,
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
)
}
}
}
}
}

View File

@ -0,0 +1,41 @@
package com.zyzf.coffeepreorder.ui.statistic
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.time.LocalDate
import java.time.format.DateTimeFormatter
class OrderStatisticViewModel(
private val orderRepository: OrderRepository
) : ViewModel() {
private val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
private var startDate: String = LocalDate.now().minusDays(1).format(formatter)
private var endDate: String = LocalDate.now().format(formatter)
var orderListUiState: Flow<List<OrderWithCoffees>> = temp()
private fun temp(): Flow<List<OrderWithCoffees>> {
var orderWithCoffees: Flow<List<OrderWithCoffees>>? = null
runBlocking {
orderWithCoffees = orderRepository.getOrdersByDate(startDate, endDate)
}
return orderWithCoffees!!
}
fun getOrdersByDate(startDate: String, endDate: String, scope: CoroutineScope) {
this.startDate = startDate
this.endDate = endDate
scope.launch {
orderListUiState = orderRepository.getOrdersByDate(startDate, endDate)
}
}
}

View File

@ -5,6 +5,7 @@
<string name="coffee_main_title">Меню</string>
<string name="coffee_cart">Корзина</string>
<string name="coffee_order">Заказ</string>
<string name="coffee_order_statistic">Статистика</string>
<string name="coffee_name">Название</string>
<string name="coffee_cost">Стоимость</string>
<string name="coffee_ingredients">Ингредиенты</string>

View File

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

4
backend/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY build/libs/yan-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

View File

@ -1,6 +1,6 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}
@ -16,16 +16,15 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
//implementation 'com.h2database:h2:2.1.212'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'org.postgresql:postgresql'
}
tasks.named('test') {
useJUnitPlatform()
}
java.targetCompatibility = JavaVersion.VERSION_17

View File

@ -1,7 +1,20 @@
package com.kalyshev.yan.coffee.repository;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.util.Pair;
import java.util.List;
public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
@Query(value = """
select c, ord.count from Coffee c
join OrderCoffeeCrossRef ord on c.id = ord.coffee.id
join Order o on o.id = ord.order.id
where o.id = :order_id
""")
public List<Object[]> findCoffeesByOrder(@Param("order_id") Long orderId);
}

View File

@ -0,0 +1,42 @@
package com.kalyshev.yan.order.controller;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
public class OrderCoffeeCrossRefDto {
private Long id;
private Long orderId;
private Long coffeeId;
private int count;
public OrderCoffeeCrossRefDto(){}
public OrderCoffeeCrossRefDto(OrderCoffeeCrossRef orderCoffeeCrossRef) {
this.id = orderCoffeeCrossRef.getId();
this.orderId = orderCoffeeCrossRef.getOrder().getId();
this.coffeeId = orderCoffeeCrossRef.getCoffee().getId();
this.count = orderCoffeeCrossRef.getCount();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getCoffeeId() {
return coffeeId;
}
public void setCoffeeId(Long coffeeId) {
this.coffeeId = coffeeId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@ -0,0 +1,70 @@
package com.kalyshev.yan.order.controller;
import com.kalyshev.yan.WebConfiguration;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import com.kalyshev.yan.order.model.OrderCoffees;
import com.kalyshev.yan.order.service.OrderService;
import jakarta.validation.Valid;
import org.springframework.data.util.Pair;
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.util.List;
import java.util.Objects;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/coffeesByOrder")
public List<Pair<CoffeeDto, Integer>> getCoffeesByOrder(
@RequestParam(value = "orderId") Long orderId
){
return orderService.findCoffeesByOrder(orderId);
}
@GetMapping("/byDate")
public List<OrderCoffees> getOrdersByDate(
@RequestParam(value = "startDate") String startDate,
@RequestParam(value = "endDate") String endDate
) throws ParseException {
return orderService.findOrdersByDate(startDate, endDate);
}
@GetMapping("/byUser")
public List<OrderDto> getOrdersByUser(
@RequestParam(value = "userId") Long userId,
@RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo,
@RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize,
@RequestParam(value = "sortBy", defaultValue = "id", required = false) String sortBy,
@RequestParam(value = "sortDir", defaultValue = "asc", required = false) String sortDir
) {
return orderService.findOrderByUser(userId, pageNo, pageSize, sortBy, sortDir).stream()
.map(OrderDto::new)
.toList();
}
@GetMapping("/coffeeCrossRef")
public List<OrderCoffeeCrossRef> getOrdersByDate() {
return orderService.findOrderCoffeeCrossRef();
}
@PostMapping("/")
public OrderDto createOrder(@RequestBody @Valid OrderDto orderDto) throws ParseException {
return new OrderDto(orderService.addOrder(orderDto.getDate(), orderDto.getSum(), orderDto.getUser().getId()));
}
@PostMapping("/coffeeCrossRef")
public void createOrderCoffeeCrossRef(@RequestBody @Valid OrderCoffeeCrossRefDto orderCoffeeCrossRefDto) {
orderService.createOrderCoffeeCrossRef(orderCoffeeCrossRefDto.getOrderId(), orderCoffeeCrossRefDto.getCoffeeId(), orderCoffeeCrossRefDto.getCount());
}
@DeleteMapping("/coffeeCrossRef/{id}")
public void deleteOrderCoffeeCrossRef(@PathVariable Long id) {
orderService.deleteOrderCoffeeCrossRef(id);
}
}

View File

@ -0,0 +1,47 @@
package com.kalyshev.yan.order.controller;
import com.kalyshev.yan.order.model.Order;
import com.kalyshev.yan.user.model.User;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
public class OrderDto {
private Long id;
private String date;
private Double sum;
private User user;
public OrderDto(){}
public OrderDto(Order order) {
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
this.id = order.getId();
this.date = format.format(order.getDate());
this.sum = order.getSum();
this.user = order.getUser();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public Double getSum() {
return sum;
}
public void setSum(Double sum) {
this.sum = sum;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@ -0,0 +1,54 @@
package com.kalyshev.yan.order.model;
import com.kalyshev.yan.user.model.User;
import jakarta.persistence.*;
import java.util.Date;
@Entity
@Table(name = "order_o")
public class Order {
@Id
@GeneratedValue
private Long id;
@Temporal(TemporalType.DATE)
private Date date;
private Double sum;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name = "user_id", unique = false)
private User user;
public Order(Date date, Double sum, User user) {
this.date = date;
this.sum = sum;
this.user = user;
}
public Order(Long id, Date date, Double sum, User user) {
this.id = id;
this.date = date;
this.sum = sum;
this.user = user;
}
public Order() {}
public Long getId() {
return id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Double getSum() {
return sum;
}
public void setSum(Double sum) {
this.sum = sum;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@ -0,0 +1,56 @@
package com.kalyshev.yan.order.model;
import com.kalyshev.yan.coffee.model.Coffee;
import jakarta.persistence.*;
import javax.crypto.Cipher;
@Entity
@Table(name = "order_coffee")
public class OrderCoffeeCrossRef {
@Id
@GeneratedValue
private Long id;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name = "order_id", unique = false)
private Order order;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name = "coffee_id", unique = false)
private Coffee coffee;
@Column(name = "count")
private int count;
public OrderCoffeeCrossRef(Order order, Coffee coffee, int count) {
this.order = order;
this.coffee = coffee;
this.count = count;
}
public OrderCoffeeCrossRef(Long id, Order order, Coffee coffee, int count) {
this.id = id;
this.order = order;
this.coffee = coffee;
this.count = count;
}
public OrderCoffeeCrossRef() {}
public Long getId() {
return id;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public Coffee getCoffee() {
return coffee;
}
public void setCoffee(Coffee coffee) {
this.coffee = coffee;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@ -0,0 +1,33 @@
package com.kalyshev.yan.order.model;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.order.controller.OrderDto;
import org.springframework.data.util.Pair;
import java.util.List;
public class OrderCoffees {
private OrderDto order;
private List<Pair<CoffeeDto, Integer>> coffees;
public OrderCoffees() {
}
public OrderCoffees(OrderDto order, List<Pair<CoffeeDto, Integer>> coffees) {
this.order = order;
this.coffees = coffees;
}
public OrderDto getOrder() {
return order;
}
public void setOrder(OrderDto order) {
this.order = order;
}
public List<Pair<CoffeeDto, Integer>> getCoffees() {
return coffees;
}
public void setCoffees(List<Pair<CoffeeDto, Integer>> coffees) {
this.coffees = coffees;
}
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.order.repository;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderCoffeeCrossRefRepository extends JpaRepository<OrderCoffeeCrossRef, Long> {
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.order.repository;
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(Long id) {
super(String.format("Order with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,30 @@
package com.kalyshev.yan.order.repository;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.order.model.Order;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Date;
import java.util.List;
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query(value = """
select * from order_o
where order_o.date >= :start_date and order_o.date <= :end_date
""", nativeQuery = true)
public List<Order> findOrdersByDate(@Param("start_date") Date startDate,
@Param("end_date") Date endDate);
@Query(value = """
select new OrderCoffeeCrossRef(ord.id, ord.order, ord.coffee, ord.count) from OrderCoffeeCrossRef ord
""")
public List<OrderCoffeeCrossRef> findOrderCoffeeCrossRef();
public Page<Order> findOrdersByUser(Long userId, Pageable pageable);
}

View File

@ -0,0 +1,130 @@
package com.kalyshev.yan.order.service;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.coffee.repository.CoffeeRepository;
import com.kalyshev.yan.order.controller.OrderCoffeeCrossRefDto;
import com.kalyshev.yan.order.controller.OrderDto;
import com.kalyshev.yan.order.model.Order;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import com.kalyshev.yan.order.model.OrderCoffees;
import com.kalyshev.yan.order.repository.OrderCoffeeCrossRefRepository;
import com.kalyshev.yan.order.repository.OrderNotFoundException;
import com.kalyshev.yan.order.repository.OrderRepository;
import com.kalyshev.yan.user.model.User;
import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.user.repository.UserRepository;
import com.kalyshev.yan.util.validation.ValidatorUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final OrderCoffeeCrossRefRepository orderCoffeeCrossRefRepository;
private final UserRepository userRepository;
private final CoffeeRepository coffeeRepository;
private final ValidatorUtil validatorUtil;
public OrderService(OrderRepository orderRepository,
OrderCoffeeCrossRefRepository orderCoffeeCrossRefRepository,
UserRepository userRepository,
CoffeeRepository coffeeRepository,
ValidatorUtil validatorUtil) {
this.orderRepository = orderRepository;
this.orderCoffeeCrossRefRepository = orderCoffeeCrossRefRepository;
this.userRepository = userRepository;
this.coffeeRepository = coffeeRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public Order addOrder(String date, Double sum, Long userId) throws ParseException {
if (!StringUtils.hasText(date)) {
throw new IllegalArgumentException("Date is null or empty");
}
if (sum <= 0) {
throw new IllegalArgumentException("Sum is null or negative");
}
if (userId <= 0) {
throw new IllegalArgumentException("UserId is null or negative");
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
final User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));
final Order order = new Order(format.parse(date), sum, user);
validatorUtil.validate(order);
return orderRepository.save(order);
}
@Transactional(readOnly = true)
public List<Pair<CoffeeDto, Integer>> findCoffeesByOrder(Long orderId) {
List<Object[]> coffees = coffeeRepository.findCoffeesByOrder(orderId);
List<Pair<CoffeeDto, Integer>> resultCoffees = new ArrayList<>();
for (Object[] coffee : coffees) {
resultCoffees.add(Pair.of(new CoffeeDto((Coffee) coffee[0]),(Integer) coffee[1]));
}
return resultCoffees;
}
@Transactional(readOnly = true)
public List<OrderCoffees> findOrdersByDate(String startDate, String endDate) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy", Locale.ROOT);
SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
Date startDateDate = formatter.parse(startDate);
Date endDateDate = formatter.parse(endDate);
List<Order> orders = orderRepository.findOrdersByDate(startDateDate, endDateDate);
List<OrderCoffees> orderCoffees = new ArrayList<OrderCoffees>();
for (Order order : orders) {
List<Object[]> coffees = coffeeRepository.findCoffeesByOrder(order.getId());
List<Pair<CoffeeDto, Integer>> resultCoffees = new ArrayList<>();
for (Object[] coffee : coffees) {
resultCoffees.add(Pair.of(new CoffeeDto((Coffee) coffee[0]),(Integer) coffee[1]));
}
orderCoffees.add(new OrderCoffees(new OrderDto(order), resultCoffees));
}
return orderCoffees;
}
@Transactional(readOnly = true)
public List<OrderCoffeeCrossRef> findOrderCoffeeCrossRef() {
List<OrderCoffeeCrossRef> orderCoffeeCrossRefs = orderRepository.findOrderCoffeeCrossRef();
return orderCoffeeCrossRefs;
}
@Transactional
public void createOrderCoffeeCrossRef(Long orderId, Long coffeeId, int count) {
final Order order = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException(orderId));
final Coffee coffee = coffeeRepository.findById(coffeeId).orElseThrow(() -> new CoffeeNotFoundException(coffeeId));
final OrderCoffeeCrossRef orderCoffeeCrossRef = new OrderCoffeeCrossRef(order, coffee, count);
validatorUtil.validate(orderCoffeeCrossRef);
orderCoffeeCrossRefRepository.save(orderCoffeeCrossRef);
}
@Transactional(readOnly = true)
public List<Order> findOrderByUser(Long userId, int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<Order> orders = orderRepository.findOrdersByUser(userId, pageable);
// get content for page object
List<Order> listOfOrders = orders.getContent();
return listOfOrders;
}
@Transactional
public void deleteOrderCoffeeCrossRef(Long id) {
orderCoffeeCrossRefRepository.deleteById(id);
}
}

View File

@ -8,7 +8,7 @@ import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "select * from \"USER_U\" where \"LOGIN\" = :login and \"PASSWORD\" = :password", nativeQuery = true)
@Query(value = "select * from user_u where login = :login and password = :password", nativeQuery = true)
public Optional<User> tryLogin(@Param("login") String login,
@Param("password") String password);

View File

@ -1,6 +1,7 @@
package com.kalyshev.yan.util.error;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.order.repository.OrderNotFoundException;
import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.util.validation.ValidationException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
@ -18,7 +19,8 @@ public class AdviceController {
@ExceptionHandler({
CoffeeNotFoundException.class,
UserNotFoundException.class,
ValidationException.class
ValidationException.class,
OrderNotFoundException.class
})
public ResponseEntity<Object> handleException(Throwable e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);

View File

@ -1,11 +1,7 @@
spring.main.banner-mode=off
#server.port=8080
spring.datasource.url=jdbc:h2:file:./data
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=true
spring.sql.init.mode=always
spring.sql.init.platform=postgres
spring.datasource.url=jdbc:postgresql://109.197.199.134:5432/coffee
spring.datasource.username=postgres
spring.datasource.password=gAiCyfGGv5TywE
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true