diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/MyServerService.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/MyServerService.kt index ad5ebca..a17d2b7 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/api/MyServerService.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/MyServerService.kt @@ -2,6 +2,7 @@ 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.OrderRemote import com.zyzf.coffeepreorder.api.model.UserRemote import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType @@ -77,8 +78,32 @@ interface MyServerService { @Path("id") id: Int, ): CoffeeRemote + @GET("/orders/byDate") + suspend fun getOrdersByDate( + @Query("startDate") startDate: String, + @Query("endDate") endDate: String, + ): List + + @POST("orders") + suspend fun createOrder( + @Body order: OrderRemote, + ): OrderRemote + + @GET("/orders/byUser") + suspend fun getOrdersByUser( + @Query("userId") userId: Int, + @Query("pageNo") page: Int, + @Query("pageSize") limit: Int, + ): List + + @GET("/orders/coffeesByOrder") + suspend fun getCoffeesByOrder( + @Query("orderId") orderId: Int, + ): List + + companion object { - private const val BASE_URL = "http://192.168.0.100:8080/api/" + private const val BASE_URL = "http://192.168.42.59:8080/api/" @Volatile private var INSTANCE: MyServerService? = null diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderCoffeeCrossRefRemote.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderCoffeeCrossRefRemote.kt new file mode 100644 index 0000000..34f8a85 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderCoffeeCrossRefRemote.kt @@ -0,0 +1,20 @@ +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, +) + +fun OrderCoffeeCrossRefRemote.toOrderCoffeeCrossRef(): OrderCoffeeCrossRef = OrderCoffeeCrossRef( + orderId, + coffeeId +) + +fun OrderCoffeeCrossRef.toOrderCoffeeCrossRefRemote(): OrderCoffeeCrossRefRemote = OrderCoffeeCrossRefRemote( + orderId, + coffeeId +) \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderCoffeeRemote.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderCoffeeRemote.kt new file mode 100644 index 0000000..b7a040e --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderCoffeeRemote.kt @@ -0,0 +1,22 @@ +package com.zyzf.coffeepreorder.api.model + +import com.zyzf.coffeepreorder.database.model.OrderWithCoffees +import kotlinx.serialization.Serializable + +@Serializable +data class OrderCoffeeRemote( + val order: OrderRemote = OrderRemote(), + val coffees: List = listOf() +) + +fun OrderCoffeeRemote.toOrdersWithCoffees(): OrderWithCoffees { + val convertedOrder = this.order.toOrder() + val convertedCoffees = this.coffees.map { it.toCoffee() } + return OrderWithCoffees(convertedOrder, convertedCoffees) +} + +fun OrderWithCoffees.toOrderCoffeeRemote(): OrderCoffeeRemote { + val convertedOrder = this.order.toOrderRemote() + val convertedCoffees = this.coffees.map { it.toCoffeeRemote() } + return OrderCoffeeRemote(convertedOrder, convertedCoffees) +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderRemote.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderRemote.kt new file mode 100644 index 0000000..725299e --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/model/OrderRemote.kt @@ -0,0 +1,26 @@ +package com.zyzf.coffeepreorder.api.model + +import com.zyzf.coffeepreorder.database.model.Order +import kotlinx.serialization.Serializable + +@Serializable +data class OrderRemote( + val id: Int = 0, + val date: String = "", + val userId: Int = 0, + val sum: Double = 0.0 +) + +fun OrderRemote.toOrder(): Order = Order( + id, + date, + userId, + sum +) + +fun Order.toOrderRemote(): OrderRemote = OrderRemote( + uid, + date, + userId, + sum +) \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/order/OrderRemoteMediator.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/order/OrderRemoteMediator.kt new file mode 100644 index 0000000..938ba02 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/order/OrderRemoteMediator.kt @@ -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() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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!!.uid, 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.uid, + type = RemoteKeyType.ORDER, + prevKey = prevKey, + nextKey = nextKey + ) + } + dbRemoteKeyRepository.createRemoteKeys(keys) + dbOrderRepository.insertOrders(orders) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { order -> + dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { order -> + dbRemoteKeyRepository.getAllRemoteKeys(order.uid, RemoteKeyType.ORDER) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.uid?.let { orderUid -> + dbRemoteKeyRepository.getAllRemoteKeys(orderUid, RemoteKeyType.ORDER) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/order/RestOrderRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/order/RestOrderRepository.kt new file mode 100644 index 0000000..2d033ff --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/order/RestOrderRepository.kt @@ -0,0 +1,60 @@ +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.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.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> { + return flowOf(service.getCoffeesByOrder(orderId).map { it.toCoffee() }) + } + + override suspend fun getOrdersByDate(startDate: String, endDate: String): Flow> { + return flowOf(service.getOrdersByDate(startDate, endDate).map { it.toOrder() }) + } + + @OptIn(ExperimentalPagingApi::class) + override fun getOrdersByUser(id: Int): Flow> { + 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) { + service.createOrder(order.toOrderRemote()).toOrder() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/api/user/RestUserRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/api/user/RestUserRepository.kt index c8be0aa..11ae1a7 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/api/user/RestUserRepository.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/api/user/RestUserRepository.kt @@ -44,18 +44,18 @@ 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().uid?.toLong()!! } override suspend fun update(user: User): Int { - return service.updateUser(user.uid, user.toUserRemote()).toUser()?.uid!! + return service.updateUser(user.uid, user.toUserRemote()).toUser().uid } override suspend fun delete(user: User) { diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/OrderDao.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/OrderDao.kt new file mode 100644 index 0000000..d08ff83 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/OrderDao.kt @@ -0,0 +1,52 @@ +package com.zyzf.coffeepreorder.database.dao + +import androidx.paging.PagingSource +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 kotlinx.coroutines.flow.Flow + +interface OrderDao { + @Query("select * from order by name collate nocase asc") + fun getAll(): PagingSource + + @Query("select * from order where order.uid = :uid") + suspend fun getByUid(uid: Int): Order? + + @Query(""" + SELECT order.* FROM order + WHERE user_id = :userId + """) + fun getOrdersByUser(userId: Int): PagingSource + + @Query(""" + SELECT coffee.* FROM coffee + INNER JOIN ordercoffeecrossref ON order.uid = ordercoffeecrossref.coffee_id + WHERE ordercoffeecrossref.order_id = :orderId + """) + fun getCoffeesByOrder(orderId: Int): Flow> + + @Query(""" + SELECT order.* FROM order + WHERE date >= :startDate and date <= :endDate + """) + fun getOrdersByDate(startDate: String, endDate: String): Flow> + + @Insert + fun insert(order: Order) + + @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() +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/OrderWithCoffees.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/OrderWithCoffees.kt new file mode 100644 index 0000000..103ed60 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/OrderWithCoffees.kt @@ -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 + @Insert + suspend fun insert(vararg order: OrderCoffeeCrossRef) + @Delete + suspend fun delete(order: OrderCoffeeCrossRef) +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Cart.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Cart.kt index ac9a86c..68e1b27 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Cart.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Cart.kt @@ -20,7 +20,7 @@ data class Cart( val uid: Int = 0, @ColumnInfo(name = "coffee_id", index = true) val coffeeId: Int, - @ColumnInfo(name = "count", index = true) + @ColumnInfo(name = "count") val count: Int = 0 ) { @Ignore diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Order.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Order.kt new file mode 100644 index 0000000..eedb660 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Order.kt @@ -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.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "order", + foreignKeys = [ForeignKey( + entity = User::class, + parentColumns = arrayOf("uid"), + 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 = "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 + ) : this(0, date, userId, 0.0) + + 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 (uid != other.uid) 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 + result = 31 * result + date.hashCode() + result = 31 * result + userId.hashCode() + result = 31 * result + sum.hashCode() + return result + } +} diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/OrderCoffeeCrossRef.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/OrderCoffeeCrossRef.kt new file mode 100644 index 0000000..b5e58af --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/OrderCoffeeCrossRef.kt @@ -0,0 +1,12 @@ +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 +) \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/OrderWithCoffees.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/OrderWithCoffees.kt new file mode 100644 index 0000000..3216f1d --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/OrderWithCoffees.kt @@ -0,0 +1,15 @@ +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 = "coffee_id", + associateBy = Junction(OrderCoffeeCrossRef::class) + ) + val coffees: List +) \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/RemoteKeys.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/RemoteKeys.kt index 7796020..39d36f7 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/model/RemoteKeys.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/RemoteKeys.kt @@ -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 } diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineOrderRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineOrderRepository.kt new file mode 100644 index 0000000..ed6370d --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineOrderRepository.kt @@ -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.dao.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> = Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = { orderDao.getOrdersByUser(id) } + ).flow + override suspend fun getCoffeesByOrder(orderId: Int): Flow> = orderDao.getCoffeesByOrder(orderId) + override suspend fun insert(order: Order) = orderDao.insert(order) + override suspend fun getOrdersByDate( + startDate: String, + endDate: String + ): Flow> = orderDao.getOrdersByDate(startDate, endDate) + fun getOrdersByUserPagingSource(id: Int): PagingSource = orderDao.getOrdersByUser(id) + suspend fun deleteAll() = orderDao.deleteAll() + suspend fun insertOrders(orders: List) = + orderDao.insert(*orders.toTypedArray()) + +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OrderRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OrderRepository.kt new file mode 100644 index 0000000..767b824 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OrderRepository.kt @@ -0,0 +1,13 @@ +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 kotlinx.coroutines.flow.Flow + +interface OrderRepository { + suspend fun getCoffeesByOrder(orderId: Int): Flow> + fun getOrdersByUser(id: Int): Flow> + suspend fun insert(order: Order) + suspend fun getOrdersByDate(startDate: String, endDate: String): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/profile/ProfileViewModel.kt index 7dfbc79..02df9a1 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/profile/ProfileViewModel.kt @@ -25,6 +25,6 @@ class ProfileViewModel( val userUid: Int? = userRepository.update(User( userUid, userLogin, userFIO, userPhone, currentUserPassw, userRole)) } - return userRepository.getByUid(userUid!!)!! + return userRepository.getByUid(userUid)!! } } diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/statistic/OrderStatistic.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/statistic/OrderStatistic.kt new file mode 100644 index 0000000..46c4505 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/statistic/OrderStatistic.kt @@ -0,0 +1,213 @@ +package com.zyzf.coffeepreorder.ui.statistic + +import android.content.Context +import android.content.res.Configuration +import android.net.Uri +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.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +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.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedIconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetState +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableDoubleStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.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.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.zyzf.coffeepreorder.CoffeeApplication +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.ui.AppViewModelProvider +import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel +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.MutableStateFlow +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OrderStatistic( + viewModel: CoffeeListViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + 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) + Scaffold( + topBar = {}, + floatingActionButton = { + if (CoffeeApplication.currentUser?.role == "admin") { + FloatingActionButton( + onClick = { + coroutineScope.launch { + //TODO + } + }, + Modifier + .padding(all = 20.dp) + ) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = "Add", + modifier = Modifier.size(20.dp) + ) + } + } + } + ) { innerPadding -> + Box (modifier = Modifier + .padding(innerPadding) + .pullRefresh(state)) { + PullRefreshIndicator(refreshing = refreshing, state = state, + modifier = Modifier + .zIndex(100f) + .align(Alignment.TopCenter) + ) + OrderList( + orderList = orderListUiState + ) + } + } +} + +@Composable +private fun OrderList( + orderList: LazyPagingItems, +) { + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally) { + items( + count = orderList.itemCount, + key = orderList.itemKey(), + contentType = orderList.itemContentType() + ) {index -> + val order = orderList[index] + order?.let { + OrderListItem( + order = order + ) + } + } + } +} + +@Composable +private fun OrderListItem( + order: OrderWithCoffees +) { + Column( + Modifier + .fillMaxWidth() + .heightIn(max = 140.dp) + .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) + } + } + +} + + +//@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 CoffeeListPreview() { +// CoffeePreorderTheme { +// Surface( +// color = MaterialTheme.colorScheme.background +// ) { +// OrderList( +// orderList = MutableStateFlow( +// PagingData.from((1..20).map { i -> Coffee.getCoffee(i) }) +// ).collectAsLazyPagingItems(), +// onAddToCartClick = {} +// ) {} +// } +// } +//} + + +//@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 CoffeeEmptyListPreview() { +// CoffeePreorderTheme { +// Surface( +// color = MaterialTheme.colorScheme.background +// ) { +// CoffeeList( +// coffeeList = MutableStateFlow( +// PagingData.empty() +// ).collectAsLazyPagingItems(), +// onAddToCartClick = {} +// ) {} +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/statistic/OrderStatisticViewModel.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/statistic/OrderStatisticViewModel.kt new file mode 100644 index 0000000..e97968f --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/statistic/OrderStatisticViewModel.kt @@ -0,0 +1,70 @@ +package com.zyzf.coffeepreorder.ui.statistic + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.paging.PagingData +import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.database.repository.CartRepository +import com.zyzf.coffeepreorder.database.repository.CoffeeRepository +import com.zyzf.coffeepreorder.ui.coffee.copyFileToSftp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream + +class OrderStatisticViewModel( + private val coffeeRepository: CoffeeRepository, + private val cartRepository: CartRepository +) : ViewModel() { + val coffeeListUiState: Flow> = coffeeRepository.getAllCoffees() + + 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) + + 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) + } +} \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index d5d65db..c5afc9e 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,6 +1,6 @@ - 192.168.0.100 + 192.168.42.59 \ No newline at end of file