diff --git a/app/src/main/java/com/example/shawarma/AppModule.kt b/app/src/main/java/com/example/shawarma/AppModule.kt index 13fd664..3237af7 100644 --- a/app/src/main/java/com/example/shawarma/AppModule.kt +++ b/app/src/main/java/com/example/shawarma/AppModule.kt @@ -50,7 +50,7 @@ object AppModule { @Provides @Singleton fun provideProductRepository(db: AppDatabase) : ProductRepository { - return ProductRepository(db.productDao(), db.orderProductDao()) + return ProductRepository(database = db, db.productDao(), db.orderProductDao()) } @Provides diff --git a/app/src/main/java/com/example/shawarma/data/api/MyServerService.kt b/app/src/main/java/com/example/shawarma/data/api/MyServerService.kt index b88d7f2..bfb5620 100644 --- a/app/src/main/java/com/example/shawarma/data/api/MyServerService.kt +++ b/app/src/main/java/com/example/shawarma/data/api/MyServerService.kt @@ -1,5 +1,6 @@ package com.example.shawarma.data.api +import com.example.shawarma.data.api.models.ProductListResponse import com.example.shawarma.data.api.models.TokenModelRemote import com.example.shawarma.data.api.models.UserModelRemote import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory @@ -8,7 +9,10 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.POST +import retrofit2.http.Path interface MyServerService { @@ -27,6 +31,15 @@ interface MyServerService { @Body user: UserModelRemote ) :UserModelRemote? + @GET("user/products/{after}") + suspend fun getProductsList(@Path("after") after: Int, @Header("Authorization") token: String) : ProductListResponse + + @GET("user/discounts/{after}") + suspend fun getDiscountsList(@Path("after") after: Int, @Header("Authorization") token: String) : ProductListResponse + + @GET("user/items/{after}") + suspend fun getItemsList(@Path("after") after: Int, @Header("Authorization") token: String) : ProductListResponse + companion object { private const val BASE_URL = "https://10.0.2.2:80/api/" diff --git a/app/src/main/java/com/example/shawarma/data/api/mediators/ProductRemoteMediator.kt b/app/src/main/java/com/example/shawarma/data/api/mediators/ProductRemoteMediator.kt new file mode 100644 index 0000000..7f7fbb0 --- /dev/null +++ b/app/src/main/java/com/example/shawarma/data/api/mediators/ProductRemoteMediator.kt @@ -0,0 +1,91 @@ +package com.example.shawarma.data.api.mediators + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.shawarma.data.api.MyServerService +import com.example.shawarma.data.api.models.ProductListResponse +import com.example.shawarma.data.api.models.toProductModel +import com.example.shawarma.data.db.AppDatabase +import com.example.shawarma.data.models.ProductModel +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class ProductRemoteMediator ( + private val database: AppDatabase, + private val serverService: MyServerService, + private val query: String, + private val token: String +) : RemoteMediator(){ + + private val productDao = database.productDao() + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + return try { + // The network load method takes an optional `after=` parameter. For every + // page after the first, we pass the last user ID to let it continue from where it + // left off. For REFRESH, pass `null` to load the first page. + var loadKey = when (loadType) { + LoadType.REFRESH -> null + // In this example, we never need to prepend, since REFRESH will always load the + // first page in the list. Immediately return, reporting end of pagination. + LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) + LoadType.APPEND -> { + val lastItem = state.lastItemOrNull() + ?: return MediatorResult.Success(endOfPaginationReached = true) + + // We must explicitly check if the last item is `null` when appending, + // since passing `null` to networkService is only valid for initial load. + // If lastItem is `null` it means no items were loaded after the initial + // REFRESH and there are no more items to load. + + lastItem.id + } + } + + // Suspending network load via Retrofit. This doesn't need to be wrapped in a + // withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter + // dispatches on a worker thread. + if (loadKey == null) { + loadKey = 0 + } + val response: ProductListResponse = when (query) { + "home" -> { + serverService.getProductsList(after = loadKey, token = token) + } + "discounts" -> { + serverService.getDiscountsList(after = loadKey, token = token) + } + "items" -> { + serverService.getItemsList(after = loadKey, token = token) + } + + else -> {ProductListResponse()} + } + + + database.withTransaction { + if (loadType == LoadType.REFRESH) { + productDao.deleteByQuery(query) + } + + // Insert new users into database, which invalidates the current + // PagingData, allowing Paging to present the updates in the DB. + val products = mutableListOf() + for (prod in response.products) { + products.add(prod.toProductModel()) + } + productDao.insertAll(products) + } + + MediatorResult.Success(endOfPaginationReached = response.nextKey == -1) + } catch (e: IOException) { + MediatorResult.Error(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/shawarma/data/api/models/ProductModelRemote.kt b/app/src/main/java/com/example/shawarma/data/api/models/ProductModelRemote.kt index 602f866..3910d35 100644 --- a/app/src/main/java/com/example/shawarma/data/api/models/ProductModelRemote.kt +++ b/app/src/main/java/com/example/shawarma/data/api/models/ProductModelRemote.kt @@ -8,13 +8,19 @@ data class ProductModelRemote( val id: Int = 0, val title: String = "", val price: Int = 0, - val oldPrice: Int? = null + val old_price: Int? = null +) + +@Serializable +data class ProductListResponse( + val products: List = listOf(), + val nextKey: Int? = null ) fun ProductModelRemote.toProductModel(): ProductModel = ProductModel( - id, title, price, oldPrice + id, title, price, old_price ) fun ProductModel.toProductModelRemote(): ProductModelRemote = ProductModelRemote( - title = title, price = price, oldPrice = oldPrice + title = title, price = price, old_price = oldPrice ) diff --git a/app/src/main/java/com/example/shawarma/data/interfaces/dao/ProductDao.kt b/app/src/main/java/com/example/shawarma/data/interfaces/dao/ProductDao.kt index 6908d82..30120af 100644 --- a/app/src/main/java/com/example/shawarma/data/interfaces/dao/ProductDao.kt +++ b/app/src/main/java/com/example/shawarma/data/interfaces/dao/ProductDao.kt @@ -13,6 +13,12 @@ interface ProductDao { @Insert suspend fun insert(product: ProductModel) + suspend fun insertAll(products: List) { + for (product in products) { + insert(product) + } + } + @Update suspend fun update(product: ProductModel) @@ -39,4 +45,25 @@ interface ProductDao { @Query("select * from products") fun getPagedItems(): PagingSource + + @Query("delete from products where products.product_old_price is null") + fun deleteAllProducts() + + @Query("delete from products where products.product_old_price is not null") + fun deleteAllDiscountProducts() + + @Query("delete from products") + fun deleteAllItems() + + fun deleteByQuery(query: String) { + if (query == "home") { + deleteAllProducts() + } + if (query == "discounts") { + deleteAllDiscountProducts() + } + if (query == "items") { + deleteAllItems() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/shawarma/data/repos/ProductRepository.kt b/app/src/main/java/com/example/shawarma/data/repos/ProductRepository.kt index 7a13b64..693e881 100644 --- a/app/src/main/java/com/example/shawarma/data/repos/ProductRepository.kt +++ b/app/src/main/java/com/example/shawarma/data/repos/ProductRepository.kt @@ -1,8 +1,12 @@ package com.example.shawarma.data.repos +import androidx.paging.ExperimentalPagingApi import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import com.example.shawarma.data.api.MyServerService +import com.example.shawarma.data.api.mediators.ProductRemoteMediator +import com.example.shawarma.data.db.AppDatabase import com.example.shawarma.data.interfaces.dao.OrderProductDao import com.example.shawarma.data.interfaces.dao.ProductDao import com.example.shawarma.data.models.ProductModel @@ -10,6 +14,7 @@ import kotlinx.coroutines.flow.Flow import javax.inject.Inject class ProductRepository @Inject constructor( + private val database: AppDatabase, private val productDao: ProductDao, private val orderProductDao: OrderProductDao ) { @@ -26,20 +31,24 @@ class ProductRepository @Inject constructor( fun getById(id: Int): Flow { return productDao.getById(id) } - fun getAllProductsPaged(): Flow> = Pager( + @OptIn(ExperimentalPagingApi::class) + fun getAllProductsPaged(token: String): Flow> = Pager( config = PagingConfig( pageSize = 6, enablePlaceholders = false ), - pagingSourceFactory = productDao::getPaged + pagingSourceFactory = productDao::getPaged, + remoteMediator = ProductRemoteMediator(database = database, serverService = MyServerService.getInstance(), query = "home", token = token) ).flow - fun getAllDiscountsPaged(): Flow> = Pager( + @OptIn(ExperimentalPagingApi::class) + fun getAllDiscountsPaged(token: String): Flow> = Pager( config = PagingConfig( pageSize = 6, enablePlaceholders = false ), - pagingSourceFactory = productDao::getPagedDiscounts + pagingSourceFactory = productDao::getPagedDiscounts, + remoteMediator = ProductRemoteMediator(database = database, serverService = MyServerService.getInstance(), query = "discounts", token = token) ).flow fun getAllItemsPaged(): Flow> = Pager( diff --git a/app/src/main/java/com/example/shawarma/screens/discount/DiscountScreen.kt b/app/src/main/java/com/example/shawarma/screens/discount/DiscountScreen.kt index 8c20d1a..acfe5d5 100644 --- a/app/src/main/java/com/example/shawarma/screens/discount/DiscountScreen.kt +++ b/app/src/main/java/com/example/shawarma/screens/discount/DiscountScreen.kt @@ -58,9 +58,13 @@ fun DiscountScreen() { @Composable fun DiscountList(){ + val preferencesManager = PreferencesManager(LocalContext.current) + val searchToken = preferencesManager.getData("token", "") + val homeViewModel: HomeViewModel = hiltViewModel() - val productsListUiState = homeViewModel.discountListUiState.collectAsLazyPagingItems() + + val productsListUiState = homeViewModel.getDiscountList(searchToken).collectAsLazyPagingItems() Box( modifier = Modifier diff --git a/app/src/main/java/com/example/shawarma/screens/home/HomeScreen.kt b/app/src/main/java/com/example/shawarma/screens/home/HomeScreen.kt index a4d9641..db8391d 100644 --- a/app/src/main/java/com/example/shawarma/screens/home/HomeScreen.kt +++ b/app/src/main/java/com/example/shawarma/screens/home/HomeScreen.kt @@ -57,9 +57,13 @@ fun HomeScreen() { @Composable fun HomeList(){ + val preferencesManager = PreferencesManager(LocalContext.current) + val searchToken = preferencesManager.getData("token", "") + val homeViewModel: HomeViewModel = hiltViewModel() - val productsListUiState = homeViewModel.productListUiState.collectAsLazyPagingItems() + + val productsListUiState = homeViewModel.getProductList(searchToken).collectAsLazyPagingItems() Box( diff --git a/app/src/main/java/com/example/shawarma/viewmodels/HomeViewModel.kt b/app/src/main/java/com/example/shawarma/viewmodels/HomeViewModel.kt index a648f31..d666e1e 100644 --- a/app/src/main/java/com/example/shawarma/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/example/shawarma/viewmodels/HomeViewModel.kt @@ -25,9 +25,13 @@ class HomeViewModel @Inject constructor( private val orderProductRepository: OrderProductRepository ) : ViewModel() { - val productListUiState: Flow> = productRepository.getAllProductsPaged() + fun getProductList(token:String): Flow> { + return productRepository.getAllProductsPaged(token) + } - val discountListUiState: Flow> = productRepository.getAllDiscountsPaged() + fun getDiscountList(token: String): Flow> { + return productRepository.getAllDiscountsPaged(token) + } fun addProductToCart(productId: Int, userId: String) {