Add generic cart models and CartUpdateManager
This commit is contained in:
parent
ec1d6e5e61
commit
28b362ce00
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ru.touchin.roboswag.cart_utils.models
|
||||||
|
|
||||||
|
abstract class CartModel<TProductModel : ProductModel> {
|
||||||
|
|
||||||
|
abstract val products: List<TProductModel>
|
||||||
|
|
||||||
|
val availableProducts: List<TProductModel>
|
||||||
|
get() = products.filter { it.isAvailable && !it.isDeleted }
|
||||||
|
|
||||||
|
val totalPrice: Int
|
||||||
|
get() = availableProducts.sumOf { it.price }
|
||||||
|
|
||||||
|
abstract fun <TCart> copyWith(
|
||||||
|
products: List<TProductModel> = this.products,
|
||||||
|
): TCart
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <TCart> asCart() = this as TCart
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ru.touchin.roboswag.cart_utils.models
|
||||||
|
|
||||||
|
abstract class ProductModel {
|
||||||
|
abstract val id: Int
|
||||||
|
abstract val countInCart: Int
|
||||||
|
abstract val price: Int
|
||||||
|
abstract val isAvailable: Boolean
|
||||||
|
abstract val isDeleted: Boolean
|
||||||
|
|
||||||
|
open val variants: List<ProductModel> = emptyList()
|
||||||
|
|
||||||
|
abstract fun <TProduct> copyWith(
|
||||||
|
countInCart: Int = this.countInCart,
|
||||||
|
isDeleted: Boolean = this.isDeleted,
|
||||||
|
): TProduct
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <TProduct> asProduct(): TProduct = this as TProduct
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ru.touchin.roboswag.cart_utils.repositories
|
||||||
|
|
||||||
|
import ru.touchin.roboswag.cart_utils.models.CartModel
|
||||||
|
import ru.touchin.roboswag.cart_utils.models.ProductModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for server-side cart repository where each request should return updated [CartModel]
|
||||||
|
*/
|
||||||
|
interface IRemoteCartRepository<TCart : CartModel<TProduct>, TProduct : ProductModel> {
|
||||||
|
|
||||||
|
suspend fun getCart(): TCart
|
||||||
|
|
||||||
|
suspend fun addProduct(product: TProduct): TCart
|
||||||
|
|
||||||
|
suspend fun removeProduct(id: Int): TCart
|
||||||
|
|
||||||
|
suspend fun editProductCount(id: Int, count: Int): TCart
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package ru.touchin.roboswag.cart_utils.repositories
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import ru.touchin.roboswag.cart_utils.models.CartModel
|
||||||
|
import ru.touchin.roboswag.cart_utils.models.ProductModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that contains StateFlow of current [CartModel] which can be subscribed in ViewModels
|
||||||
|
*/
|
||||||
|
class LocalCartRepository<TCart : CartModel<TProduct>, TProduct : ProductModel>(
|
||||||
|
initialCart: TCart
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val _currentCart = MutableStateFlow(initialCart)
|
||||||
|
val currentCart = _currentCart.asStateFlow()
|
||||||
|
|
||||||
|
fun updateCart(cart: TCart) {
|
||||||
|
_currentCart.value = cart
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addProduct(product: TProduct) {
|
||||||
|
updateCartProducts {
|
||||||
|
add(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeProduct(id: Int) {
|
||||||
|
updateCartProducts {
|
||||||
|
val product = find { it.id == id }
|
||||||
|
remove(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editProductCount(id: Int, count: Int) {
|
||||||
|
updateCartProducts {
|
||||||
|
updateProduct(id) { copyWith(countInCart = count) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markProductDeleted(id: Int) {
|
||||||
|
updateCartProducts {
|
||||||
|
updateProduct(id) { copyWith(isDeleted = true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreDeletedProduct(id: Int) {
|
||||||
|
updateCartProducts {
|
||||||
|
updateProduct(id) { copyWith(isDeleted = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCartProducts(updateAction: MutableList<TProduct>.() -> Unit) {
|
||||||
|
_currentCart.update { cart ->
|
||||||
|
cart.copyWith(products = cart.products.toMutableList().apply(updateAction))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<TProduct>.updateProduct(id: Int, updateAction: TProduct.() -> TProduct) {
|
||||||
|
val index = indexOfFirst { it.id == id }
|
||||||
|
if (index >= 0) this[index] = updateAction.invoke(this[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package ru.touchin.roboswag.cart_utils.update_manager
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import ru.touchin.roboswag.cart_utils.models.CartModel
|
||||||
|
import ru.touchin.roboswag.cart_utils.models.ProductModel
|
||||||
|
import ru.touchin.roboswag.cart_utils.repositories.IRemoteCartRepository
|
||||||
|
import ru.touchin.roboswag.cart_utils.repositories.LocalCartRepository
|
||||||
|
import ru.touchin.roboswag.cart_utils.requests_qeue.Request
|
||||||
|
import ru.touchin.roboswag.cart_utils.requests_qeue.RequestsQueue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines local and remote cart update actions
|
||||||
|
*/
|
||||||
|
open class CartUpdateManager<TCart : CartModel<TProduct>, TProduct : ProductModel>(
|
||||||
|
private val localCartRepository: LocalCartRepository<TCart, TProduct>,
|
||||||
|
private val remoteCartRepository: IRemoteCartRepository<TCart, TProduct>,
|
||||||
|
private val maxRequestAttemptsCount: Int = MAX_REQUEST_CART_ATTEMPTS_COUNT,
|
||||||
|
private val errorHandler: (Throwable) -> Unit = {},
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_REQUEST_CART_ATTEMPTS_COUNT = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestsQueue = RequestsQueue<Request<TCart>>()
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var lastRemoteCart: TCart? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun initCartRequestsQueue(
|
||||||
|
coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO),
|
||||||
|
) {
|
||||||
|
requestsQueue.initRequestsExecution(coroutineScope) { request ->
|
||||||
|
runCatching {
|
||||||
|
lastRemoteCart = request.invoke()
|
||||||
|
if (!requestsQueue.hasPendingRequests()) updateLocalCartWithRemote()
|
||||||
|
}.onFailure { error ->
|
||||||
|
errorHandler.invoke(error)
|
||||||
|
requestsQueue.clearQueue()
|
||||||
|
tryToGetRemoteCartAgain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun addProduct(product: TProduct, restoreDeleted: Boolean = false) {
|
||||||
|
with(localCartRepository) {
|
||||||
|
if (restoreDeleted) restoreDeletedProduct(product.id) else addProduct(product)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestsQueue.addToQueue {
|
||||||
|
remoteCartRepository.addProduct(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun removeProduct(id: Int, markDeleted: Boolean = false) {
|
||||||
|
with(localCartRepository) {
|
||||||
|
if (markDeleted) markProductDeleted(id) else removeProduct(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestsQueue.addToQueue {
|
||||||
|
remoteCartRepository.removeProduct(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun editProductCount(id: Int, count: Int) {
|
||||||
|
localCartRepository.editProductCount(id, count)
|
||||||
|
|
||||||
|
requestsQueue.addToQueue {
|
||||||
|
remoteCartRepository.editProductCount(id, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun completelyDeleteProduct(id: Int) {
|
||||||
|
localCartRepository.removeProduct(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun tryToGetRemoteCartAgain() {
|
||||||
|
repeat(maxRequestAttemptsCount) {
|
||||||
|
runCatching {
|
||||||
|
lastRemoteCart = remoteCartRepository.getCart()
|
||||||
|
updateLocalCartWithRemote()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLocalCartWithRemote() {
|
||||||
|
val remoteCart = lastRemoteCart ?: return
|
||||||
|
val remoteProducts = remoteCart.products
|
||||||
|
val localProducts = localCartRepository.currentCart.value.products
|
||||||
|
|
||||||
|
val newProductsFromRemoteCart = remoteProducts.filter { remoteProduct ->
|
||||||
|
localProducts.none { it.id == remoteProduct.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
val mergedProducts = localProducts.mapNotNull { localProduct ->
|
||||||
|
val sameRemoteProduct = remoteProducts.find { it.id == localProduct.id }
|
||||||
|
|
||||||
|
when {
|
||||||
|
sameRemoteProduct != null -> sameRemoteProduct
|
||||||
|
localProduct.isDeleted -> localProduct
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val mergedCart = remoteCart.copyWith<TCart>(products = mergedProducts + newProductsFromRemoteCart)
|
||||||
|
localCartRepository.updateCart(mergedCart)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue