5 вроде готова

This commit is contained in:
Ismailov_Rovshan 2023-12-23 03:38:15 +04:00
parent e9bc8e9792
commit f82fd803ce
34 changed files with 679 additions and 87 deletions

View File

@ -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")

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MyApp"
@ -12,7 +13,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"

View File

@ -2,7 +2,17 @@ package com.example.myapplication
import android.app.Application
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.repository.OfflineCategoryRepository
import com.example.myapplication.database.repository.OfflineProductRepository
class MyApp : Application() {
val db by lazy { AppDb.getInstance(this) }
val productRepository: OfflineProductRepository by lazy {
OfflineProductRepository(db.productDao())
}
val categoryRepository: OfflineCategoryRepository by lazy {
OfflineCategoryRepository(db.categoryDao())
}
}

View File

@ -0,0 +1,96 @@
package com.example.myapplication.api
import com.example.myapplication.api.model.CategoryRemote
import com.example.myapplication.api.model.ProductRemote
import com.example.myapplication.api.model.UserProductListRemote
import com.example.myapplication.api.model.UserProductRemote
import com.example.myapplication.api.model.UserRemote
import com.example.myapplication.database.entities.Category
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.Interceptor.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
interface MyServerService {
@GET("products")
suspend fun getProducts(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<ProductRemote>
@GET("categories")
suspend fun getCategories(): List<CategoryRemote>
@POST("products")
suspend fun createProduct(
@Body product: ProductRemote,
): ProductRemote
@GET("users")
suspend fun getAllUsers() : List<UserRemote>
@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<UserRemote>
@GET("users/{id}/userProductCart?_expand=product")
suspend fun getUserProductCartById(
@Path("id") id: Int): List<UserProductListRemote>
@GET("userProductCart")
suspend fun getProductCart(
@Query("userId") userId: Int,
@Query("productId") productId: Int
): List<UserProductRemote>
@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 }
}
}
}
}

View File

@ -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,
)

View File

@ -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
)
}

View File

@ -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,
)
}

View File

@ -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
)
}

View File

@ -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<Int, Product>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Product>
): 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<Int, Product>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { item ->
dbRemoteKeyRepository.getAllRemoteKeys(item.productId!!, RemoteKeyType.ITEM)
}
}
private suspend fun getRemoteKeyForFirstProduct(state: PagingState<Int, Product>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { item ->
dbRemoteKeyRepository.getAllRemoteKeys(item.productId!!, RemoteKeyType.ITEM)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Product>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.productId?.let { itemUid ->
dbRemoteKeyRepository.getAllRemoteKeys(itemUid, RemoteKeyType.ITEM)
}
}
}
}

View File

@ -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<PagingData<Product>> = 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<Product> = productDao.getById(id)
@OptIn(ExperimentalPagingApi::class)
override fun getByCategory(category_id: Int): Flow<PagingData<Product>> = 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<Product>) = productDao.insertAll(products)
override suspend fun clearProducts() = productDao.deleteAll()
}

View File

@ -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<Category> {
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")
}
}

View File

@ -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<User> {
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())
}
}

View File

@ -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<Category?>(null) }
LaunchedEffect(Unit) {
categoryViewModel.getAll().collect{categs ->
categories.clear()
categs.forEach{
categories.add(it)
}
withContext(Dispatchers.IO) {
categories.addAll(categoryViewModel.getAll())
}
if(id != 0) {

View File

@ -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,{})
}
}

View File

@ -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()

View File

@ -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<Category>() }
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{

View File

@ -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))

View File

@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface CategoryDao {
@Query("select * from categories")
fun getAll(): Flow<List<Category>>
fun getAll(): List<Category>
@Insert
suspend fun insert(category: Category)

View File

@ -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<RemoteKeys>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -11,21 +11,22 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from users")
fun getAll(): Flow<List<User>>
fun getAll(): List<User>
@Query("select * from users where users.userId = :id")
fun getById(id: Int): Flow<User>
fun getById(id: Int): User
@Query("select * from users where users.login = :login and users.password = :password")
fun getByAuth(login: String, password: String): Flow<User?>
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<UserWithCartProduct>
fun getUserProductCartById(id: Int): UserWithCartProduct
@Insert
suspend fun addProductCart(userProduct: UserProductCart)

View File

@ -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?
)

View File

@ -4,6 +4,6 @@ import com.example.myapplication.database.entities.Category
import kotlinx.coroutines.flow.Flow
interface CategoryRepository {
fun getAll(): Flow<List<Category>>
suspend fun getAll(): List<Category>
suspend fun insert(category: Category)
}

View File

@ -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<List<Category>> = categoryDao.getAll()
override suspend fun getAll(): List<Category> = categoryDao.getAll()
override suspend fun insert(category: Category) = categoryDao.insert(category)
}

View File

@ -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<Product>) = productDao.insertAll(products)
override suspend fun clearProducts() = productDao.deleteAll()
}

View File

@ -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<RemoteKeys>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -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<List<User>> = userDao.getAll()
override fun getById(id: Int): Flow<User> = userDao.getById(id)
override fun getByAuth(login: String, password: String): Flow<User?> = userDao.getByAuth(login, password)
override fun getUserProductCartById(id: Int): Flow<UserWithCartProduct> = userDao.getUserProductCartById(id)
override suspend fun getAll(): List<User> = 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)
}

View File

@ -9,4 +9,6 @@ interface ProductRepository {
fun getById(id: Int): Flow<Product>
fun getByCategory(category_id: Int): Flow<PagingData<Product>>
suspend fun insert(product: Product)
suspend fun insertAll(products: List<Product>)
suspend fun clearProducts()
}

View File

@ -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<RemoteKeys>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -6,12 +6,11 @@ import com.example.myapplication.database.entities.UserWithCartProduct
import kotlinx.coroutines.flow.Flow
interface UserRepository {
fun getAll(): Flow<List<User>>
fun getById(id: Int): Flow<User>
fun getByAuth(login: String, password: String): Flow<User?>
fun getUserProductCartById(id: Int): Flow<UserWithCartProduct>
suspend fun getAll(): List<User>
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)
}

View File

@ -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<List<Category>> {
suspend fun getAll(): List<Category> {
return categoryRepository.getAll()
}
}

View File

@ -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 <T: ViewModel> create(
modelClass: Class<T>,
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
}
}
}

View File

@ -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<T>,
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<List<User>> {
suspend fun getAll(): List<User> {
return userRepository.getAll()
}
fun getById(id: Int): Flow<User> {
suspend fun getById(id: Int): User {
return userRepository.getById(id)
}
fun getByAuth(login: String, password: String): Flow<User?> {
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<UserWithCartProduct> {
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)
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>

View File

@ -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
}