done with android app??

This commit is contained in:
Zyzf 2023-12-22 21:13:33 +04:00
parent 105378ca52
commit 710b20c3af
46 changed files with 467 additions and 215 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,8 +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
@ -79,31 +83,43 @@ interface MyServerService {
): CoffeeRemote
@GET("/orders/byDate")
suspend fun getOrdersByDate(
fun getOrdersByDate(
@Query("startDate") startDate: String,
@Query("endDate") endDate: String,
): List<OrderRemote>
): List<OrderCoffeeRemote>
@POST("orders")
@POST("/order/")
suspend fun createOrder(
@Body order: OrderRemote,
): OrderRemote
@GET("/orders/byUser")
@GET("/order/byUser")
suspend fun getOrdersByUser(
@Query("userId") userId: Int,
@Query("pageNo") page: Int,
@Query("pageSize") limit: Int,
): List<OrderRemote>
@GET("/orders/coffeesByOrder")
@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.42.59:8080/api/"
private const val BASE_URL = "http://192.168.0.100:8080/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

@ -9,7 +9,7 @@ data class OrderCoffeeRemote(
val coffees: List<CoffeeRemote> = listOf()
)
fun OrderCoffeeRemote.toOrdersWithCoffees(): OrderWithCoffees {
fun OrderCoffeeRemote.toOrderWithCoffees(): OrderWithCoffees {
val convertedOrder = this.order.toOrder()
val convertedCoffees = this.coffees.map { it.toCoffee() }
return OrderWithCoffees(convertedOrder, convertedCoffees)

View File

@ -19,7 +19,7 @@ fun OrderRemote.toOrder(): Order = Order(
)
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
uid,
orderId,
date,
userId,
sum

View File

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

View File

@ -57,7 +57,7 @@ class OrderRemoteMediator(
}
try {
val orders = service.getOrdersByUser(CoffeeApplication.currentUser!!.uid, page, state.config.pageSize).map { it.toOrder() }
val orders = service.getOrdersByUser(CoffeeApplication.currentUser!!.userId, page, state.config.pageSize).map { it.toOrder() }
val endOfPaginationReached = orders.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
@ -68,7 +68,7 @@ class OrderRemoteMediator(
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = orders.map {
RemoteKeys(
entityId = it.uid,
entityId = it.orderId,
type = RemoteKeyType.ORDER,
prevKey = prevKey,
nextKey = nextKey
@ -88,14 +88,14 @@ class OrderRemoteMediator(
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Order>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { order ->
dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER)
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.uid, RemoteKeyType.ORDER)
dbRemoteKeyRepository.getAllRemoteKeys(order.orderId, RemoteKeyType.ORDER)
}
}
@ -103,7 +103,7 @@ class OrderRemoteMediator(
state: PagingState<Int, Order>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { orderUid ->
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

@ -9,10 +9,12 @@ 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
@ -30,8 +32,8 @@ class RestOrderRepository(
return flowOf(service.getCoffeesByOrder(orderId).map { it.toCoffee() })
}
override suspend fun getOrdersByDate(startDate: String, endDate: String): Flow<List<Order>> {
return flowOf(service.getOrdersByDate(startDate, endDate).map { it.toOrder() })
override fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>> {
return flowOf(service.getOrdersByDate(startDate, endDate).map { it.toOrderWithCoffees() })
}
@OptIn(ExperimentalPagingApi::class)
@ -54,7 +56,7 @@ class RestOrderRepository(
).flow
}
override suspend fun insert(order: Order) {
service.createOrder(order.toOrderRemote()).toOrder()
override suspend fun insert(order: Order): Long {
return service.createOrder(order.toOrderRemote()).toOrder().orderId.toLong()
}
}

View File

@ -51,14 +51,14 @@ class RestUserRepository(
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,13 +14,16 @@ 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")
@Query("select cart.count from cart JOIN coffee on coffee.coffee_id = cart.coffee_id where coffee.coffee_id = :coffeeId")
fun getCountForCoffee(coffeeId: Int): Double
@Insert

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

@ -1,42 +1,46 @@
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 by name collate nocase asc")
@Query("select * from `order` order by order_id collate nocase asc")
fun getAll(): PagingSource<Int, Order>
@Query("select * from order where order.uid = :uid")
@Query("select * from `order` where `order`.order_id = :uid")
suspend fun getByUid(uid: Int): Order?
@Query("""
SELECT order.* FROM order
SELECT `order`.* FROM `order`
WHERE user_id = :userId
""")
fun getOrdersByUser(userId: Int): PagingSource<Int, Order>
@Query("""
SELECT coffee.* FROM coffee
INNER JOIN ordercoffeecrossref ON order.uid = ordercoffeecrossref.coffee_id
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
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<Order>>
fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>>
@Insert
fun insert(order: Order)
fun insert(order: Order): Long
@Insert
suspend fun insert(vararg order: Order)
@ -47,6 +51,6 @@ interface OrderDao {
@Delete
suspend fun delete(order: Order)
@Query("delete from order")
@Query("delete from `order`")
suspend fun deleteAll()
}

View File

@ -11,7 +11,7 @@ interface OrderWithCoffees {
@Query("select * from ordercoffeecrossref")
fun getAll(): List<OrderCoffeeCrossRef>
@Insert
suspend fun insert(vararg order: OrderCoffeeCrossRef)
suspend fun insert(vararg orderCoffee: OrderCoffeeCrossRef)
@Delete
suspend fun delete(order: OrderCoffeeCrossRef)
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

@ -9,7 +9,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "cart", foreignKeys = [
ForeignKey(
entity = Cart::class,
parentColumns = ["uid"],
parentColumns = ["cart_id"],
childColumns = ["coffee_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
@ -17,7 +17,8 @@ import androidx.room.PrimaryKey
])
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")
@ -33,13 +34,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

@ -11,16 +11,16 @@ import androidx.room.PrimaryKey
tableName = "order",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = arrayOf("uid"),
parentColumns = arrayOf("user_id"),
childColumns = arrayOf("user_id"),
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)],
indices = [Index(value = ["user_id"])]
)]
)
data class Order(
@PrimaryKey(autoGenerate = true)
val uid: Int = 0,
@ColumnInfo(name = "order_id")
val orderId: Int = 0,
@ColumnInfo(name = "date")
var date: String,
@ColumnInfo(name = "user_id", index = true)
@ -31,8 +31,9 @@ data class Order(
@Ignore
constructor(
date: String,
userId: Int
) : this(0, date, userId, 0.0)
userId: Int,
sum: Double
) : this(0, date, userId, sum)
companion object {
fun getOrder(index: Int = 0): Order {
@ -49,14 +50,14 @@ data class Order(
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Order
if (uid != other.uid) return false
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 = uid
var result = orderId
result = 31 * result + date.hashCode()
result = 31 * result + userId.hashCode()
result = 31 * result + sum.hashCode()

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,6 +9,7 @@ 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)

View File

@ -17,6 +17,8 @@ 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)

View File

@ -6,7 +6,7 @@ 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.dao.OrderWithCoffees
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
@ -21,10 +21,10 @@ class OfflineOrderRepository(private val orderDao: OrderDao) : OrderRepository {
).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(
override fun getOrdersByDate(
startDate: String,
endDate: String
): Flow<List<Order>> = orderDao.getOrdersByDate(startDate, endDate)
): 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>) =

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

@ -3,11 +3,12 @@ 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)
suspend fun getOrdersByDate(startDate: String, endDate: String): Flow<List<Order>>
suspend fun insert(order: Order): Long
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 {
@ -28,6 +30,14 @@ object AppViewModelProvider {
initializer {
ProfileViewModel(coffeeApplication().container.userRestRepository)
}
initializer {
OrderStatisticViewModel(coffeeApplication().container.orderRestRepository)
}
initializer {
OrderViewModel(coffeeApplication().container.orderRestRepository,
coffeeApplication().container.orderCoffeesRestRepository,
coffeeApplication().container.cartRepository)
}
}
}

View File

@ -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: Double = getCoffeeCount(coffee.coffeeId)
val currentCoffeeName: String = if (coffeeCount > 1) {
coffee.name + " x" + coffeeCount.toString()
} else {

View File

@ -16,6 +16,6 @@ class CartViewModel(
}
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

@ -142,17 +142,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
@ -92,6 +96,7 @@ fun Navbar(
}
}
@Composable
fun Navhost(
navController: NavHostController,
@ -109,6 +114,10 @@ fun Navhost(
composable(Screen.Profile.route) { Profile(navController) }
composable(Screen.Cart.route) { Cart(navController) }
composable(Screen.Order.route) { Order(navController) }
if (CoffeeApplication.currentUser != null && CoffeeApplication.currentUser!!.role == "admin") {
composable(Screen.OrderStatistic.route) { OrderStatistic() }
}
}
}

View File

@ -3,6 +3,7 @@ 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
@ -32,6 +33,9 @@ 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 {

View File

@ -33,6 +33,7 @@ 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
@ -40,11 +41,16 @@ 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.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Calendar
@ -53,18 +59,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 +116,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.CoffeeList.route)
},
icon = { Icon(Icons.Filled.Add, "Оформить заказ") },
text = { Text(text = "Оформить заказ") },
modifier = Modifier
@ -169,17 +183,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.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
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-yyy", 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))
}
}
}

View File

@ -204,7 +204,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

View File

@ -3,11 +3,13 @@ package com.zyzf.coffeepreorder.ui.statistic
import android.content.Context
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -15,11 +17,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
@ -29,6 +34,9 @@ import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.Create
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
@ -40,9 +48,12 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SheetState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf
@ -60,6 +71,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
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.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.PagingData
@ -74,30 +87,37 @@ import com.zyzf.coffeepreorder.R
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.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel
import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import eu.bambooapps.material3.pullrefresh.PullRefreshIndicator
import eu.bambooapps.material3.pullrefresh.pullRefresh
import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.util.Calendar
import java.util.Date
import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OrderStatistic(
viewModel: CoffeeListViewModel = viewModel(factory = AppViewModelProvider.Factory)
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.coffeeListUiState.collectAsLazyPagingItems()
val openDialog = remember { mutableStateOf(false) }
var refreshing by remember { mutableStateOf(false) }
fun refresh() = coroutineScope.launch {
refreshing = true
orderListUiState.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
val orderListUiState = viewModel.orderListUiState.collectAsState(initial = listOf())
val formatter = SimpleDateFormat("dd-MM-yyy", Locale.ROOT)
val startDate = remember { mutableStateOf(formatter.format(LocalDate.now().minusDays(1)))}
val endDate = remember { mutableStateOf(formatter.format(LocalDate.now()))}
val isStartDate = remember { mutableStateOf(true)}
Scaffold(
topBar = {},
floatingActionButton = {
@ -105,7 +125,7 @@ fun OrderStatistic(
FloatingActionButton(
onClick = {
coroutineScope.launch {
//TODO
viewModel.getOrdersByDate(startDate.value, endDate.value)
}
},
Modifier
@ -113,7 +133,7 @@ fun OrderStatistic(
) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Add",
contentDescription = "Perform",
modifier = Modifier.size(20.dp)
)
}
@ -121,41 +141,89 @@ fun OrderStatistic(
}
) { innerPadding ->
Box (modifier = Modifier
.padding(innerPadding)
.pullRefresh(state)) {
PullRefreshIndicator(refreshing = refreshing, state = state,
modifier = Modifier
.zIndex(100f)
.align(Alignment.TopCenter)
.padding(innerPadding)) {
StartEndDateBlock(
startDate = startDate,
endDate = endDate,
onStartDateClick = {
isStartDate.value = true
openDatePicker.value = true
},
onEndDateClick = {
isStartDate.value = false
openDatePicker.value = true
}
)
OrderList(
orderList = orderListUiState
)
}
DateDialog(
startDate = startDate,
endDate = endDate,
isStartDate = isStartDate,
formatter = formatter,
openDatePicker = openDatePicker,
datePickerState = datePickerState
)
}
}
@Composable
private fun OrderList(
orderList: State<List<OrderWithCoffees>>,
) {
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally) {
items(items = orderList.value) {order ->
OrderListItem(
order = order
)
}
}
}
@Composable
private fun OrderList(
orderList: LazyPagingItems<OrderWithCoffees>,
private fun StartEndDateBlock(
startDate: MutableState<String>,
endDate: MutableState<String>,
onStartDateClick: () -> Unit,
onEndDateClick: () -> Unit
) {
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally) {
items(
count = orderList.itemCount,
key = orderList.itemKey(),
contentType = orderList.itemContentType()
) {index ->
val order = orderList[index]
order?.let {
OrderListItem(
order = order
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 140.dp)
.padding(bottom = 10.dp, top = 10.dp)) {
Row() {
Button(onClick = {onStartDateClick()},
shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(text = "От")
}
Text(text = startDate.value)
Button(onClick = {onEndDateClick()},
shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(text = "До")
}
Text(text = endDate.value)
}
}
}
@Composable
private fun OrderListItem(
order: OrderWithCoffees
@ -167,14 +235,65 @@ private fun OrderListItem(
.padding(bottom = 10.dp, top = 10.dp)) {
Text(text = order.order.userId.toString(), fontSize = 25.sp)
Text(text = String.format("%.2f", order.coffees.sumOf { it.cost }), fontSize = 20.sp)
order.coffees.map {
Text(text = it.name, fontSize = 15.sp)
Row() {
order.coffees.map {
Text(text = it.name, fontSize = 15.sp)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DateDialog(
startDate: MutableState<String>,
endDate: MutableState<String>,
isStartDate: MutableState<Boolean>,
formatter: SimpleDateFormat,
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) {
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)
)
}
}
}
}
}
//@RequiresApi(Build.VERSION_CODES.O)
//@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
@ -183,17 +302,16 @@ private fun OrderListItem(
// Surface(
// color = MaterialTheme.colorScheme.background
// ) {
// OrderList(
// OrderStatistic(
// orderList = MutableStateFlow(
// PagingData.from((1..20).map { i -> Coffee.getCoffee(i) })
// ).collectAsLazyPagingItems(),
// onAddToCartClick = {}
// ).collectAsLazyPagingItems()
// ) {}
// }
// }
//}
//
//
//@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
@ -202,7 +320,7 @@ private fun OrderListItem(
// Surface(
// color = MaterialTheme.colorScheme.background
// ) {
// CoffeeList(
// OrderList(
// coffeeList = MutableStateFlow(
// PagingData.empty<Coffee>()
// ).collectAsLazyPagingItems(),

View File

@ -4,11 +4,17 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import com.zyzf.coffeepreorder.ui.coffee.copyFileToSftp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@ -16,55 +22,23 @@ import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.util.Calendar
import java.util.Date
class OrderStatisticViewModel(
private val coffeeRepository: CoffeeRepository,
private val cartRepository: CartRepository
private val orderRepository: OrderRepository
) : ViewModel() {
val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAllCoffees()
private val formatter = SimpleDateFormat("dd-MM-yyy")
private var startDate: String = formatter.format(LocalDate.now().minusDays(1))
private var endDate: String = formatter.format(LocalDate.now())
suspend fun addCoffeeToCart(coffeeUid: Int) {
cartRepository.insertCoffee(coffeeUid, 1)
}
suspend fun createCoffee(coffee: Coffee, imageUri: Uri, context: Context) {
val newCoffee: Long = coffeeRepository.insert(coffee)
val inputStream = context.contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream)
var orderListUiState: Flow<List<OrderWithCoffees>> = orderRepository.getOrdersByDate(startDate, endDate)
val f = File(context.cacheDir, "coffee_image_$newCoffee.png")
withContext(Dispatchers.IO) {
f.createNewFile()
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos)
val bitmapdata = bos.toByteArray()
val fos = FileOutputStream(f)
fos.write(bitmapdata)
fos.flush()
fos.close()
}
copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images")
}
suspend fun editCoffee(coffee: Coffee, imageUri: Any?, context: Context) {
val editedCoffee: Int = coffeeRepository.update(coffee)
if (imageUri !is Uri) return
val inputStream = context.contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream)
val f = File(context.cacheDir, "coffee_image_$editedCoffee.png")
withContext(Dispatchers.IO) {
f.createNewFile()
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos)
val bitmapdata = bos.toByteArray()
val fos = FileOutputStream(f)
fos.write(bitmapdata)
fos.flush()
fos.close()
}
copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images")
}
suspend fun deleteCoffee(coffee: Coffee) {
coffeeRepository.delete(coffee)
fun getOrdersByDate(startDate: String, endDate: String) {
this.startDate = startDate
this.endDate = endDate
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.42.59</domain>
<domain includeSubdomains="true">192.168.0.100</domain>
</domain-config>
</network-security-config>