From f82fd803ceddebce201739700024b2f9eeb2982f Mon Sep 17 00:00:00 2001 From: Ismailov_Rovshan Date: Sat, 23 Dec 2023 03:38:15 +0400 Subject: [PATCH] =?UTF-8?q?5=20=D0=B2=D1=80=D0=BE=D0=B4=D0=B5=20=D0=B3?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 9 ++ app/src/main/AndroidManifest.xml | 5 +- .../java/com/example/myapplication/MyApp.kt | 10 ++ .../myapplication/api/MyServerService.kt | 96 ++++++++++++++++ .../myapplication/api/model/CategoryRemote.kt | 20 ++++ .../myapplication/api/model/ProductRemote.kt | 44 +++++++ .../api/model/UserProductRemote.kt | 20 ++++ .../myapplication/api/model/UserRemote.kt | 30 +++++ .../api/repository/ProductRemoteMediator.kt | 108 ++++++++++++++++++ .../api/repository/ProductRestRepository.kt | 76 ++++++++++++ .../api/repository/RestCategoryRepository.kt | 37 ++++++ .../api/repository/UserRestRepository.kt | 50 ++++++++ .../myapplication/components/AddProduct.kt | 9 +- .../example/myapplication/components/Cart.kt | 34 ++---- .../example/myapplication/components/Main.kt | 17 +-- .../myapplication/components/Сategory.kt | 19 +-- .../example/myapplication/database/AppDb.kt | 8 +- .../myapplication/database/dao/CategoryDao.kt | 2 +- .../database/dao/RemoteKeysDao.kt | 21 ++++ .../myapplication/database/dao/UserDao.kt | 9 +- .../database/entities/RemoteKeys.kt | 25 ++++ .../database/repository/CategoryRepository.kt | 2 +- .../repository/OfflineCategoryRepository.kt | 3 +- .../repository/OfflineProductRepository.kt | 3 + .../repository/OfflineRemoteKeyRepository.kt | 16 +++ .../repository/OfflineUserRepository.kt | 12 +- .../database/repository/ProductRepository.kt | 2 + .../repository/RemoteKeyRepository.kt | 10 ++ .../database/repository/UserRepository.kt | 9 +- .../viewModels/CategoryViewModel.kt | 10 +- .../viewModels/ProductViewModel.kt | 22 +++- .../myapplication/viewModels/UserViewModel.kt | 21 ++-- .../main/res/xml/network_security_config.xml | 6 + build.gradle.kts | 1 + 34 files changed, 679 insertions(+), 87 deletions(-) create mode 100644 app/src/main/java/com/example/myapplication/api/MyServerService.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/ProductRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/UserProductRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/UserRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/ProductRemoteMediator.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/ProductRestRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/UserRestRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/database/dao/RemoteKeysDao.kt create mode 100644 app/src/main/java/com/example/myapplication/database/entities/RemoteKeys.kt create mode 100644 app/src/main/java/com/example/myapplication/database/repository/OfflineRemoteKeyRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/database/repository/RemoteKeyRepository.kt create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6d30417..f0aeaa0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") + id("org.jetbrains.kotlin.plugin.serialization") } android { @@ -69,6 +70,14 @@ dependencies { implementation("androidx.room:room-ktx:$room_version") implementation("androidx.room:room-paging:$room_version") + // retrofit + val retrofitVersion = "2.9.0" + implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") + implementation("com.squareup.okhttp3:logging-interceptor:4.11.0") + implementation("androidx.paging:paging-compose:3.2.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 55b6581..f83a715 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + + tools:targetApi="31" + android:networkSecurityConfig="@xml/network_security_config"> + + + @GET("categories") + suspend fun getCategories(): List + + @POST("products") + suspend fun createProduct( + @Body product: ProductRemote, + ): ProductRemote + + @GET("users") + suspend fun getAllUsers() : List + + @GET("users/{id}") + suspend fun getUserById(@Path("id") id: Int) : UserRemote + + @GET("users") + suspend fun getUserByAuth( + @Query("login") login: String, + @Query("pass") pass: String) : List + + @GET("users/{id}/userProductCart?_expand=product") + suspend fun getUserProductCartById( + @Path("id") id: Int): List + + @GET("userProductCart") + suspend fun getProductCart( + @Query("userId") userId: Int, + @Query("productId") productId: Int + ): List + + + @POST("users") + suspend fun createUser(@Body user: UserRemote) + + @POST("userProductCart") + suspend fun addProductCart(@Body userProduct: UserProductRemote) + + @DELETE("userProductCart/{id}") + suspend fun deleteCartProduct( + @Path("id") id: Int, + ) + + companion object { + private const val BASE_URL = "http://10.0.2.2:8079/" + + @Volatile + private var INSTANCE: MyServerService? = null + + fun getInstance(): MyServerService { + return INSTANCE ?: synchronized(this) { + val logger = HttpLoggingInterceptor() + logger.level = HttpLoggingInterceptor.Level.BASIC + val client = OkHttpClient.Builder() + .addInterceptor(logger) + .build() + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + .create(MyServerService::class.java) + .also { INSTANCE = it } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt b/app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt new file mode 100644 index 0000000..623b540 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt @@ -0,0 +1,20 @@ +package com.example.myapplication.api.model + +import com.example.myapplication.database.entities.Category +import kotlinx.serialization.Serializable + +@Serializable +data class CategoryRemote( + val id: Int = 0, + val name: String, +) + +fun CategoryRemote.toCategory(): Category = Category( + id, + name, +) + +fun Category.toCategoryRemote(): CategoryRemote = CategoryRemote( + id!!, + name, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/ProductRemote.kt b/app/src/main/java/com/example/myapplication/api/model/ProductRemote.kt new file mode 100644 index 0000000..c27388c --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/ProductRemote.kt @@ -0,0 +1,44 @@ +package com.example.myapplication.api.model + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.example.myapplication.database.entities.Product +import kotlinx.serialization.Serializable +import java.io.ByteArrayOutputStream + +@Serializable +data class ProductRemote( + val id: Int = 0, + val name: String, + val info: String, + val price: Double, + val img: ByteArray, + val categoryId: Int +) + +fun ProductRemote.toProduct(): Product { + val imgBitmap: Bitmap = BitmapFactory.decodeByteArray(img, 0, img.size) + return Product( + id, + name, + info, + price, + imgBitmap, + categoryId + ) +} + +fun Product.toProductRemote(): ProductRemote { + val outputStream = ByteArrayOutputStream(); + img.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val imgByteArr: ByteArray = outputStream.toByteArray() + + return ProductRemote( + 0, + name, + info, + price, + imgByteArr, + categoryId + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/UserProductRemote.kt b/app/src/main/java/com/example/myapplication/api/model/UserProductRemote.kt new file mode 100644 index 0000000..ffaba2f --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/UserProductRemote.kt @@ -0,0 +1,20 @@ +package com.example.myapplication.api.model + +import com.example.myapplication.database.entities.UserProductCart +import kotlinx.serialization.Serializable + +@Serializable +data class UserProductRemote( + val id: Int, + val userId: Int, + val productId: Int +) + +fun UserProductCart.toUserProductRemote(): UserProductRemote { + return UserProductRemote( + 0, + userId, + productId, + ) +} + diff --git a/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt b/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt new file mode 100644 index 0000000..106b7b5 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt @@ -0,0 +1,30 @@ +package com.example.myapplication.api.model + +import com.example.myapplication.database.entities.User +import kotlinx.serialization.Serializable + +@Serializable +data class UserRemote( + var id: Int, + val name: String, + val login: String, + val password: String +) + +fun UserRemote.toUser(): User { + return User( + id, + name, + login, + password + ) +} + +fun User.toUserRemote(): UserRemote { + return UserRemote( + 0, + name, + login, + password + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/ProductRemoteMediator.kt b/app/src/main/java/com/example/myapplication/api/repository/ProductRemoteMediator.kt new file mode 100644 index 0000000..9652868 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/ProductRemoteMediator.kt @@ -0,0 +1,108 @@ +package com.example.myapplication.api.repository + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.model.toProduct +import com.example.myapplication.database.AppDb +import com.example.myapplication.database.entities.Product +import com.example.myapplication.database.entities.RemoteKeyType +import com.example.myapplication.database.entities.RemoteKeys +import com.example.myapplication.database.repository.OfflineProductRepository +import com.example.myapplication.database.repository.OfflineRemoteKeyRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class ProductRemoteMediator( + private val service: MyServerService, + private val dbProductRepository: OfflineProductRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val categoryRestRepository: RestCategoryRepository, + private val database: AppDb +) : 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 = getRemoteKeyForFirstProduct(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastProduct(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val products = service.getProducts(page, state.config.pageSize) + val endOfPaginationReached = products.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ITEM) + dbProductRepository.clearProducts() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = products.map { + RemoteKeys( + entityId = it.id, + type = RemoteKeyType.ITEM, + prevKey = prevKey, + nextKey = nextKey + ) + } + categoryRestRepository.getAll() + dbRemoteKeyRepository.createRemoteKeys(keys) + dbProductRepository.insertAll(products.map { it.toProduct() }) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastProduct(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { item -> + dbRemoteKeyRepository.getAllRemoteKeys(item.productId!!, RemoteKeyType.ITEM) + } + } + + private suspend fun getRemoteKeyForFirstProduct(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { item -> + dbRemoteKeyRepository.getAllRemoteKeys(item.productId!!, RemoteKeyType.ITEM) + } + } + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.productId?.let { itemUid -> + dbRemoteKeyRepository.getAllRemoteKeys(itemUid, RemoteKeyType.ITEM) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/ProductRestRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/ProductRestRepository.kt new file mode 100644 index 0000000..a7c0574 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/ProductRestRepository.kt @@ -0,0 +1,76 @@ +package com.example.myapplication.api.repository + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.model.toProductRemote +import com.example.myapplication.database.AppDb +import com.example.myapplication.database.dao.ProductDao +import com.example.myapplication.database.entities.Product +import com.example.myapplication.database.repository.OfflineProductRepository +import com.example.myapplication.database.repository.OfflineRemoteKeyRepository +import com.example.myapplication.database.repository.ProductRepository +import kotlinx.coroutines.flow.Flow + +class ProductRestRepository( + private val service: MyServerService, + private val productDao: ProductDao, + private val dbProductRepository: OfflineProductRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val categoryRestRepository: RestCategoryRepository, + private val database: AppDb +): ProductRepository { + @OptIn(ExperimentalPagingApi::class) + override fun getAll(): Flow> = Pager( + config = PagingConfig( + pageSize = 8, + prefetchDistance = 2, + enablePlaceholders = true, + initialLoadSize = 12, + maxSize = 24 + ), + remoteMediator = ProductRemoteMediator( + service, + dbProductRepository, + dbRemoteKeyRepository, + categoryRestRepository, + database, + ), + pagingSourceFactory = { + productDao.getAll() + } + ).flow + + override fun getById(id: Int): Flow = productDao.getById(id) + + @OptIn(ExperimentalPagingApi::class) + override fun getByCategory(category_id: Int): Flow> = Pager( + config = PagingConfig( + pageSize = 8, + prefetchDistance = 2, + enablePlaceholders = true, + initialLoadSize = 12, + maxSize = 24 + ), + remoteMediator = ProductRemoteMediator( + service, + dbProductRepository, + dbRemoteKeyRepository, + categoryRestRepository, + database, + ), + pagingSourceFactory = { + productDao.getByCategory(category_id) + } + ).flow + + override suspend fun insert(product: Product) { + service.createProduct(product.toProductRemote()) + } + + override suspend fun insertAll(products: List) = productDao.insertAll(products) + + override suspend fun clearProducts() = productDao.deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt new file mode 100644 index 0000000..5315852 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt @@ -0,0 +1,37 @@ +package com.example.myapplication.api.repository + +import com.example.myapplication.database.entities.Category +import com.example.myapplication.database.repository.CategoryRepository +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.model.toCategory +import com.example.myapplication.database.repository.OfflineCategoryRepository + + +class RestCategoryRepository( + private val service: MyServerService, + private val dbCategoryRepository: OfflineCategoryRepository, +): CategoryRepository { + override suspend fun getAll(): List { + val existCategories = dbCategoryRepository.getAll().associateBy { it.id }.toMutableMap() + + kotlin.runCatching { + service.getCategories() + .map { it.toCategory() } + .forEach { category -> + val existCategory = existCategories[category.id] + if (existCategory == null) { + dbCategoryRepository.insert(category) + } + existCategories[category.id] = category + } + } + .onFailure { + + } + return existCategories.map { it.value }.sortedBy { it.id } + } + + override suspend fun insert(category: Category) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/UserRestRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/UserRestRepository.kt new file mode 100644 index 0000000..e2e5b16 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/UserRestRepository.kt @@ -0,0 +1,50 @@ +package com.example.myapplication.api.repository + +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.model.toProductList +import com.example.myapplication.api.model.toUser +import com.example.myapplication.api.model.toUserProductRemote +import com.example.myapplication.api.model.toUserRemote +import com.example.myapplication.database.entities.User +import com.example.myapplication.database.entities.UserProductCart +import com.example.myapplication.database.entities.UserWithCartProduct + +import com.example.myapplication.database.repository.UserRepository +class UserRestRepository( + private val service: MyServerService +): UserRepository { + override suspend fun getAll(): List { + return service.getAllUsers().map { it.toUser() } + } + + override suspend fun getById(id: Int): User { + return service.getUserById(id).toUser() + } + + override suspend fun getByAuth(login: String, password: String): User? { + val ans = service.getUserByAuth(login, password) + return if(ans.isEmpty()) null + else ans[0].toUser() + } + + override suspend fun getUserProductCartById(id: Int): UserWithCartProduct { + val products = service.getUserProductCartById(id).map { it.toProductList() } + return UserWithCartProduct(service.getUserById(id).toUser(), products) + } + + override suspend fun deleteCartProduct(userId: Int, productId: Int) { + val product = service.getProductCart(userId, productId)[0] + service.deleteCartProduct(product.id) + } + + override suspend fun insert(user: User) { + service.createUser(user.toUserRemote()) + } + + override suspend fun addProductCart(userProduct: UserProductCart) { + val product = service.getProductCart(userProduct.userId, userProduct.productId) + if(!product.isEmpty()) throw Exception("Уже добавлено") + + service.addProductCart(userProduct.toUserProductRemote()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/components/AddProduct.kt b/app/src/main/java/com/example/myapplication/components/AddProduct.kt index 64fd2c9..ad215c1 100644 --- a/app/src/main/java/com/example/myapplication/components/AddProduct.kt +++ b/app/src/main/java/com/example/myapplication/components/AddProduct.kt @@ -43,7 +43,9 @@ import com.example.myapplication.database.entities.Category import com.example.myapplication.database.entities.Product import com.example.myapplication.viewModels.CategoryViewModel import com.example.myapplication.viewModels.ProductViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import myColor1 import myColor2 @@ -89,11 +91,8 @@ fun AddProduct( val categoryState = remember { mutableStateOf(null) } LaunchedEffect(Unit) { - categoryViewModel.getAll().collect{categs -> - categories.clear() - categs.forEach{ - categories.add(it) - } + withContext(Dispatchers.IO) { + categories.addAll(categoryViewModel.getAll()) } if(id != 0) { diff --git a/app/src/main/java/com/example/myapplication/components/Cart.kt b/app/src/main/java/com/example/myapplication/components/Cart.kt index bd50393..81e8bce 100644 --- a/app/src/main/java/com/example/myapplication/components/Cart.kt +++ b/app/src/main/java/com/example/myapplication/components/Cart.kt @@ -38,14 +38,18 @@ fun Cart(navController: NavController, userViewModel: UserViewModel = viewModel( val sumPrice = remember { mutableStateOf(0.0) } LaunchedEffect(Unit) { + if(userViewModel.getUserId() == 0) { + navController.navigate("authorization") + } - userViewModel.getUserProductCartById(userViewModel.getUserId()).collect {data -> - products.clear() - sumPrice.value = 0.0; - data.products.forEach { - sumPrice.value += it.price - products.add(it) - } + withContext(Dispatchers.IO) { + val data = userViewModel.getUserProductsCartById(userViewModel.getUserId()) + products.clear() + sumPrice.value = 0.0; + data.products.forEach { + sumPrice.value += it.price + products.add(it) + } } } @@ -54,23 +58,9 @@ fun Cart(navController: NavController, userViewModel: UserViewModel = viewModel( TextNice("Корзина") } -// item { -// for (product in products){ -// ProductCardInCart(product.name, product.price, product.img){ -// coroutineScope.launch { -// userViewModel.deleteCartProduct(userViewModel.getUserId(), product.productId!!) -// } -// } -// } -// } item{ products.forEach{ - ProductCardInCart(it.name, it.price, it.img){ - coroutineScope.launch { - Log.d("delete", "мама макса б") - userViewModel.deleteCartProduct(userViewModel.getUserId(), it.productId!!) - } - } + ProductCardInCart(it.name, it.price, it.img,{}) } } diff --git a/app/src/main/java/com/example/myapplication/components/Main.kt b/app/src/main/java/com/example/myapplication/components/Main.kt index 42fd325..cdaf181 100644 --- a/app/src/main/java/com/example/myapplication/components/Main.kt +++ b/app/src/main/java/com/example/myapplication/components/Main.kt @@ -61,7 +61,6 @@ fun Main( productViewModel: ProductViewModel = viewModel(factory = ProductViewModel.factory), userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory) ) { - val context = LocalContext.current val coroutineScope = rememberCoroutineScope() @@ -123,26 +122,16 @@ fun Main( createProductCard( it.name, it.price, it.img, { navController.navigate("product/" + it.productId)}){ coroutineScope.launch { kotlin.runCatching { - userViewModel.addProductCart( - UserProductCart( - userViewModel.getUserId(), - it.productId!! - ) - ) + userViewModel.addProductCart(UserProductCart(userViewModel.getUserId(), it.productId!!)) } .onSuccess { - val toast = - Toast.makeText(context, "Добавлено", Toast.LENGTH_SHORT) + val toast = Toast.makeText(context, "Добавлено", Toast.LENGTH_SHORT) toast.show() delay(500) toast.cancel() } .onFailure { - val toast = Toast.makeText( - context, - "Уже есть в корзине", - Toast.LENGTH_SHORT - ) + val toast = Toast.makeText(context, "Уже есть в корзине", Toast.LENGTH_SHORT) toast.show() delay(500) toast.cancel() diff --git a/app/src/main/java/com/example/myapplication/components/Сategory.kt b/app/src/main/java/com/example/myapplication/components/Сategory.kt index e0b61c2..7ff65aa 100644 --- a/app/src/main/java/com/example/myapplication/components/Сategory.kt +++ b/app/src/main/java/com/example/myapplication/components/Сategory.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.example.myapplication.components.templates.CatalogItems @@ -19,12 +20,17 @@ import com.example.myapplication.components.templates.CategoryItem import com.example.myapplication.database.AppDb import com.example.myapplication.database.entities.Category import com.example.myapplication.database.entities.Product +import com.example.myapplication.viewModels.CategoryViewModel +import com.example.myapplication.viewModels.UserViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable -fun Сategory(navController: NavController) { +fun Сategory(navController: NavController, + userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory), + categoryViewModel: CategoryViewModel = viewModel(factory = CategoryViewModel.factory) +) { Column( modifier = Modifier.fillMaxWidth().padding(start = 10.dp, end = 10.dp, top = 20.dp,), horizontalAlignment = Alignment.End @@ -34,13 +40,12 @@ fun Сategory(navController: NavController) { val categories = remember { mutableStateListOf() } LaunchedEffect(Unit) { + if(userViewModel.getUserId() == 0) { + navController.navigate("authorization") + } + withContext(Dispatchers.IO) { - AppDb.getInstance(context).categoryDao().getAll().collect { data -> - categories.clear() - data.forEach { - categories.add(it) - } - } + categories.addAll(categoryViewModel.getAll()) } } categories.forEach{ diff --git a/app/src/main/java/com/example/myapplication/database/AppDb.kt b/app/src/main/java/com/example/myapplication/database/AppDb.kt index e68afaa..7b98fba 100644 --- a/app/src/main/java/com/example/myapplication/database/AppDb.kt +++ b/app/src/main/java/com/example/myapplication/database/AppDb.kt @@ -11,10 +11,12 @@ import androidx.sqlite.db.SupportSQLiteDatabase import com.example.myapplication.R import com.example.myapplication.database.dao.CategoryDao import com.example.myapplication.database.dao.ProductDao +import com.example.myapplication.database.dao.RemoteKeysDao import com.example.myapplication.database.dao.UserDao import com.example.myapplication.database.entities.Category import com.example.myapplication.database.entities.Converters import com.example.myapplication.database.entities.Product +import com.example.myapplication.database.entities.RemoteKeys import com.example.myapplication.database.entities.User import com.example.myapplication.database.entities.UserProductCart @@ -23,12 +25,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -@Database(entities = [User::class, Product::class, Category:: class, UserProductCart::class], version = 1, exportSchema = false) +@Database(entities = [User::class, Product::class, Category:: class, UserProductCart::class , RemoteKeys::class], version = 1, exportSchema = false) @TypeConverters(Converters::class) abstract class AppDb: RoomDatabase(){ abstract fun userDao(): UserDao abstract fun productDao(): ProductDao abstract fun categoryDao(): CategoryDao + abstract fun remoteKeysDao(): RemoteKeysDao + companion object { private const val DB_NAME: String = "myApp2.db" @@ -96,7 +100,7 @@ abstract class AppDb: RoomDatabase(){ )) val userDao = database.userDao() - userDao.insert(User(1, "Иванов И.И", "ivanov","ivanov")) + userDao.insert(User(5, "Иванов И.И", "ivanov","ivanov")) database.userDao().addProductCart(UserProductCart(1, 1)) database.userDao().addProductCart(UserProductCart(1, 3)) //database.userDao().addProductCart(UserProductCart(1, 2)) diff --git a/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt b/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt index 2f55688..2f8b8db 100644 --- a/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt +++ b/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow @Dao interface CategoryDao { @Query("select * from categories") - fun getAll(): Flow> + fun getAll(): List @Insert suspend fun insert(category: Category) diff --git a/app/src/main/java/com/example/myapplication/database/dao/RemoteKeysDao.kt b/app/src/main/java/com/example/myapplication/database/dao/RemoteKeysDao.kt new file mode 100644 index 0000000..10a9274 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/dao/RemoteKeysDao.kt @@ -0,0 +1,21 @@ +package com.example.myapplication.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.example.myapplication.database.entities.RemoteKeyType +import com.example.myapplication.database.entities.RemoteKeys + + +@Dao +interface RemoteKeysDao { + @Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type") + suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(remoteKey: List) + + @Query("DELETE FROM remote_keys WHERE type = :type") + suspend fun clearRemoteKeys(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt b/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt index 0ee04f5..cc82bbb 100644 --- a/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt +++ b/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt @@ -11,21 +11,22 @@ import kotlinx.coroutines.flow.Flow @Dao interface UserDao { @Query("select * from users") - fun getAll(): Flow> + fun getAll(): List @Query("select * from users where users.userId = :id") - fun getById(id: Int): Flow + fun getById(id: Int): User @Query("select * from users where users.login = :login and users.password = :password") - fun getByAuth(login: String, password: String): Flow + fun getByAuth(login: String, password: String): User? @Query("delete from userproductcart where userproductcart.userId == :userId and userproductcart.productId == :productId") suspend fun deleteCartProduct(userId: Int, productId: Int) + @Insert suspend fun insert(user: User) @Query("select * from users where users.userId = :id") - fun getUserProductCartById(id: Int): Flow + fun getUserProductCartById(id: Int): UserWithCartProduct @Insert suspend fun addProductCart(userProduct: UserProductCart) diff --git a/app/src/main/java/com/example/myapplication/database/entities/RemoteKeys.kt b/app/src/main/java/com/example/myapplication/database/entities/RemoteKeys.kt new file mode 100644 index 0000000..adeab7f --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/entities/RemoteKeys.kt @@ -0,0 +1,25 @@ +package com.example.myapplication.database.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import androidx.room.TypeConverters + +enum class RemoteKeyType(private val type: String) { + ITEM(Product::class.simpleName ?: "Product"); + + @TypeConverter + fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value } + + @TypeConverter + fun fromRemoteKeyType(value: RemoteKeyType) = value.type +} + +@Entity(tableName = "remote_keys") +data class RemoteKeys( + @PrimaryKey val entityId: Int, + @TypeConverters(RemoteKeyType::class) + val type: RemoteKeyType, + val prevKey: Int?, + val nextKey: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt index 840bd93..ce81cf3 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt @@ -4,6 +4,6 @@ import com.example.myapplication.database.entities.Category import kotlinx.coroutines.flow.Flow interface CategoryRepository { - fun getAll(): Flow> + suspend fun getAll(): List suspend fun insert(category: Category) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt index 1dc3108..64842f6 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt @@ -2,9 +2,8 @@ package com.example.myapplication.database.repository import com.example.myapplication.database.dao.CategoryDao import com.example.myapplication.database.entities.Category -import kotlinx.coroutines.flow.Flow class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository { - override fun getAll(): Flow> = categoryDao.getAll() + override suspend fun getAll(): List = categoryDao.getAll() override suspend fun insert(category: Category) = categoryDao.insert(category) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineProductRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineProductRepository.kt index 857fdb6..a5cd2b4 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineProductRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineProductRepository.kt @@ -38,4 +38,7 @@ class OfflineProductRepository(private val productDao: ProductDao) : ProductRepo ).flow } override suspend fun insert(product: Product) = productDao.insert(product) + override suspend fun insertAll(products: List) = productDao.insertAll(products) + + override suspend fun clearProducts() = productDao.deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineRemoteKeyRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineRemoteKeyRepository.kt new file mode 100644 index 0000000..603b159 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineRemoteKeyRepository.kt @@ -0,0 +1,16 @@ +package com.example.myapplication.database.repository + +import com.example.myapplication.database.dao.RemoteKeysDao +import com.example.myapplication.database.entities.RemoteKeyType +import com.example.myapplication.database.entities.RemoteKeys + +class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository { + override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) = + remoteKeysDao.getRemoteKeys(id, type) + + override suspend fun createRemoteKeys(remoteKeys: List) = + remoteKeysDao.insertAll(remoteKeys) + + override suspend fun deleteRemoteKey(type: RemoteKeyType) = + remoteKeysDao.clearRemoteKeys(type) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt index 1081416..34112c7 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt @@ -7,11 +7,13 @@ import com.example.myapplication.database.entities.UserWithCartProduct import kotlinx.coroutines.flow.Flow class OfflineUserRepository(private val userDao: UserDao) : UserRepository { - override fun getAll(): Flow> = userDao.getAll() - override fun getById(id: Int): Flow = userDao.getById(id) - override fun getByAuth(login: String, password: String): Flow = userDao.getByAuth(login, password) - override fun getUserProductCartById(id: Int): Flow = userDao.getUserProductCartById(id) + override suspend fun getAll(): List = userDao.getAll() + override suspend fun getById(id: Int): User = userDao.getById(id) + override suspend fun getByAuth(login: String, password: String): User? = userDao.getByAuth(login, password) + override suspend fun getUserProductCartById(id: Int): UserWithCartProduct = userDao.getUserProductCartById(id) + override suspend fun deleteCartProduct(userId: Int, productId: Int) = userDao.deleteCartProduct(userId, productId) + override suspend fun insert(user: User) = userDao.insert(user) - override suspend fun addProductCart(userItem: UserProductCart) = userDao.addProductCart(userItem) + override suspend fun addProductCart(userProduct: UserProductCart) = userDao.addProductCart(userProduct) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/ProductRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/ProductRepository.kt index dbf49ac..9d10a8f 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/ProductRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/ProductRepository.kt @@ -9,4 +9,6 @@ interface ProductRepository { fun getById(id: Int): Flow fun getByCategory(category_id: Int): Flow> suspend fun insert(product: Product) + suspend fun insertAll(products: List) + suspend fun clearProducts() } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/RemoteKeyRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/RemoteKeyRepository.kt new file mode 100644 index 0000000..68f6254 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/repository/RemoteKeyRepository.kt @@ -0,0 +1,10 @@ +package com.example.myapplication.database.repository + +import com.example.myapplication.database.entities.RemoteKeyType +import com.example.myapplication.database.entities.RemoteKeys + +interface RemoteKeyRepository { + suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys? + suspend fun createRemoteKeys(remoteKeys: List) + suspend fun deleteRemoteKey(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt index b056e43..35054fc 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt @@ -6,12 +6,11 @@ import com.example.myapplication.database.entities.UserWithCartProduct import kotlinx.coroutines.flow.Flow interface UserRepository { - fun getAll(): Flow> - fun getById(id: Int): Flow - fun getByAuth(login: String, password: String): Flow - fun getUserProductCartById(id: Int): Flow + suspend fun getAll(): List + suspend fun getById(id: Int): User + suspend fun getByAuth(login: String, password: String): User? + suspend fun getUserProductCartById(id: Int): UserWithCartProduct suspend fun deleteCartProduct(userId: Int, productId: Int) suspend fun insert(user: User) suspend fun addProductCart(userItem: UserProductCart) - } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/viewModels/CategoryViewModel.kt b/app/src/main/java/com/example/myapplication/viewModels/CategoryViewModel.kt index 0716976..8d8cb22 100644 --- a/app/src/main/java/com/example/myapplication/viewModels/CategoryViewModel.kt +++ b/app/src/main/java/com/example/myapplication/viewModels/CategoryViewModel.kt @@ -4,9 +4,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.CreationExtras import com.example.myapplication.MyApp +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.repository.RestCategoryRepository import com.example.myapplication.database.entities.Category import com.example.myapplication.database.repository.CategoryRepository -import com.example.myapplication.database.repository.OfflineCategoryRepository import kotlinx.coroutines.flow.Flow class CategoryViewModel ( @@ -19,14 +20,13 @@ class CategoryViewModel ( extras: CreationExtras ): T { - val db = - (checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) as MyApp).db - return CategoryViewModel(OfflineCategoryRepository(db.categoryDao())) as T + val app: MyApp = (checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) as MyApp) + return CategoryViewModel(RestCategoryRepository(MyServerService.getInstance(), app.categoryRepository)) as T } } } - fun getAll(): Flow> { + suspend fun getAll(): List { return categoryRepository.getAll() } } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/viewModels/ProductViewModel.kt b/app/src/main/java/com/example/myapplication/viewModels/ProductViewModel.kt index 1720436..29aaad0 100644 --- a/app/src/main/java/com/example/myapplication/viewModels/ProductViewModel.kt +++ b/app/src/main/java/com/example/myapplication/viewModels/ProductViewModel.kt @@ -8,8 +8,12 @@ import androidx.lifecycle.viewmodel.CreationExtras import androidx.paging.PagingData import androidx.paging.cachedIn import com.example.myapplication.MyApp +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.repository.ProductRestRepository +import com.example.myapplication.api.repository.RestCategoryRepository import com.example.myapplication.database.entities.Product import com.example.myapplication.database.repository.OfflineProductRepository +import com.example.myapplication.database.repository.OfflineRemoteKeyRepository import com.example.myapplication.database.repository.ProductRepository import kotlinx.coroutines.flow.Flow @@ -20,10 +24,20 @@ class ProductViewModel( val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory{ override fun create( modelClass: Class, - extras: CreationExtras - ) : T { - val db = (checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) as MyApp).db - return ProductViewModel(OfflineProductRepository(db.productDao())) as T + extras: CreationExtras) : T { + + val app: MyApp = (checkNotNull(extras[APPLICATION_KEY]) as MyApp) + + return ProductViewModel( + ProductRestRepository( + MyServerService.getInstance(), + app.db.productDao(), + app.productRepository, + OfflineRemoteKeyRepository(app.db.remoteKeysDao()), + RestCategoryRepository(MyServerService.getInstance(), app.categoryRepository), + app.db + ) + ) as T } } } diff --git a/app/src/main/java/com/example/myapplication/viewModels/UserViewModel.kt b/app/src/main/java/com/example/myapplication/viewModels/UserViewModel.kt index 74c9083..6a45f7f 100644 --- a/app/src/main/java/com/example/myapplication/viewModels/UserViewModel.kt +++ b/app/src/main/java/com/example/myapplication/viewModels/UserViewModel.kt @@ -5,6 +5,8 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.CreationExtras import com.example.myapplication.GlobalUser import com.example.myapplication.MyApp +import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.repository.UserRestRepository import com.example.myapplication.database.entities.User import com.example.myapplication.database.entities.UserProductCart import com.example.myapplication.database.entities.UserWithCartProduct @@ -21,9 +23,7 @@ class UserViewModel( modelClass: Class, extras: CreationExtras ) : T { - - val db = (checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) as MyApp).db - return UserViewModel(OfflineUserRepository(db.userDao())) as T + return UserViewModel(UserRestRepository(MyServerService.getInstance())) as T } } } @@ -36,27 +36,30 @@ class UserViewModel( GlobalUser.getInstance().userId = id } - fun getAll(): Flow> { + suspend fun getAll(): List { return userRepository.getAll() } - fun getById(id: Int): Flow { + suspend fun getById(id: Int): User { return userRepository.getById(id) } - fun getByAuth(login: String, password: String): Flow { + suspend fun getByAuth(login: String, password: String): User? { return userRepository.getByAuth(login, password) } + + suspend fun deleteCartProduct(userId: Int, productId: Int) { userRepository.deleteCartProduct(userId, productId) } - fun getUserProductCartById(id: Int): Flow { + + suspend fun getUserProductsCartById(id: Int): UserWithCartProduct { return userRepository.getUserProductCartById(id) } suspend fun insert(user: User) { userRepository.insert(user) } - suspend fun addProductCart(userItem: UserProductCart) { - userRepository.addProductCart(userItem) + suspend fun addProductCart(userProduct: UserProductCart) { + userRepository.addProductCart(userProduct) } } \ 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 new file mode 100644 index 0000000..eb7159c --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + 10.0.2.2 + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bd1eb8a..04a426d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,5 @@ plugins { id("com.android.application") version "8.1.1" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "1.8.20" apply false } \ No newline at end of file