6 Commits
Lab03 ... Lab05

Author SHA1 Message Date
c684afce70 win 2023-12-23 09:04:24 +04:00
97bafd0491 win 2023-12-23 04:21:36 +04:00
f82fd803ce 5 вроде готова 2023-12-23 03:38:15 +04:00
e9bc8e9792 Сдал 2023-12-20 11:51:58 +04:00
0d8d318a0c Готово 2023-12-20 00:54:49 +04:00
1095dbaa47 Хорошо идёт 2023-12-19 22:59:30 +04:00
51 changed files with 1446 additions and 301 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")
@@ -76,4 +85,7 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation ("androidx.paging:paging-compose:3.2.1")
implementation ("androidx.paging:paging-runtime:3.2.1")
}

View File

@@ -1,8 +1,10 @@
<?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"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@@ -11,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

@@ -0,0 +1,12 @@
package com.example.myapplication
class GlobalUser {
var userId: Int = 0
companion object {
private var INSTANCE: GlobalUser? = null
fun getInstance(): GlobalUser {
if(INSTANCE == null) INSTANCE = GlobalUser()
return INSTANCE!!
}
}
}

View File

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,158 @@
package com.example.myapplication.components
import ButtonNice
import TextNice
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.components.common.myInput
import com.example.myapplication.components.templates.DropDown
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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddProduct(
navController: NavController,
id: Int = 0,
itemViewModel: ProductViewModel = viewModel(factory = ProductViewModel.factory),
categoryViewModel: CategoryViewModel = viewModel(factory = CategoryViewModel.factory)
)
{
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val img = remember { mutableStateOf<Bitmap>(
BitmapFactory.decodeResource(context.resources,
R.drawable.product5
)) }
val imageData = remember { mutableStateOf<Uri?>(null) }
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
imageData.value = uri
}
imageData.value?.let {
if (Build.VERSION.SDK_INT < 28) {
img.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
img.value = ImageDecoder.decodeBitmap(source)
}
}
val categories = remember{ mutableListOf<Category>() }
val nameState = remember { mutableStateOf("") }
val infoState = remember { mutableStateOf("") }
val priceState = remember { mutableStateOf("") }
val categoryState = remember { mutableStateOf<Category?>(null) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
categories.addAll(categoryViewModel.getAll())
}
if(id != 0) {
itemViewModel.getById(id).collect{
nameState.value = it.name
}
}
}
Column(modifier = Modifier
.fillMaxSize()
.padding(start = 15.dp, end = 10.dp).verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextNice("Добавление товара")
myInput("Название", nameState)
myInput("Описание", infoState)
myInput("Цена", priceState)
DropDown(categories, categoryState)
Image(
bitmap = img.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(350.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
// Button(
// onClick = {
// launcher.launch("image/*")
// },
// modifier = Modifier
// .fillMaxWidth()
// .padding(top = 10.dp)) {
// Text("Выбрать картинку", fontSize = 20.sp)
// }
ButtonNice("Выбрать картинку", myColor1){
launcher.launch("image/*")
}
ButtonNice("Сохранить", myColor2){
coroutineScope.launch {
itemViewModel.insert(Product(null, nameState.value,infoState.value, priceState.value.toDouble(), img.value, categoryState.value!!.id!!))
navController.navigate("main/0")
}
}
// Button(
// onClick = {
// coroutineScope.launch {
// itemViewModel.insert(Product(null, nameState.value,infoState.value, priceState.value.toDouble(), img.value, categoryState.value!!.id!!))
// navController.navigate("main/0")
// }
// },
// modifier = Modifier
// .fillMaxWidth()
// .padding(top = 10.dp)) {
// Text("Сохранить", fontSize = 20.sp)
// }
}
}

View File

@@ -3,6 +3,7 @@ package com.example.myapplication.components
import ButtonNice
import TextNice
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -12,31 +13,40 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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 com.example.myapplication.components.funs.ProductCardInCart
import com.example.myapplication.components.funs.createProductCard
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Product
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import myColor4
@Composable
fun Cart(navController: NavController){
fun Cart(navController: NavController, userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)){
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val products = remember { mutableStateListOf<Product>() }
val sumPrice = remember { mutableStateOf(0.0) }
LaunchedEffect(Unit) {
if(userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
withContext(Dispatchers.IO) {
val data = userViewModel.getUserProductsCartById(userViewModel.getUserId())
products.clear()
sumPrice.value = 0.0;
AppDb.getInstance(context).userDao().getUserProductCartById(1).products.forEach {
data.products.forEach {
sumPrice.value += it.price
products.add(it)
}
@@ -48,9 +58,15 @@ fun Cart(navController: NavController){
TextNice("Корзина")
}
item {
for (product in products){
ProductCardInCart(product.name, product.price, product.img, { navController.navigate("product/" + product.productId)})
item{
products.forEach{
ProductCardInCart(it.name, it.price, it.img){
coroutineScope.launch {
userViewModel.deleteCartProduct(userViewModel.getUserId(), it.productId!!)
sumPrice.value -= it.price
products.remove(it)
}
}
}
}

View File

@@ -1,89 +1,144 @@
package com.example.myapplication.components
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.database.entities.Product
import androidx.compose.runtime.LaunchedEffect
import androidx.paging.compose.itemKey
import com.example.myapplication.R
import com.example.myapplication.components.funs.createProductCard
import com.example.myapplication.database.AppDb
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.example.myapplication.database.entities.UserProductCart
import com.example.myapplication.viewModels.ProductViewModel
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import myColor3
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Main(navController: NavController, categoryId: Int) {
fun Main(
navController: NavController,
catalogId: Int,
productViewModel: ProductViewModel = viewModel(factory = ProductViewModel.factory),
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)
) {
val context = LocalContext.current
val products = remember { mutableStateListOf<Product>() }
val coroutineScope = rememberCoroutineScope()
if (categoryId == 0){
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
AppDb.getInstance(context).productDao().getAll().collect { data ->
products.clear()
data.forEach{
products.add(it)
}
}
}
}
} else {
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
AppDb.getInstance(context).categoryDao().getProductsByCategory(categoryId).collect { data ->
products.clear()
data.forEach {
products.add(it)
}
}
}
}
val products = when (catalogId) {
0 -> productViewModel.getAll().collectAsLazyPagingItems()
else -> productViewModel.getByCategory(catalogId).collectAsLazyPagingItems()
}
LaunchedEffect(Unit) {
if (userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
) {
// item { OutlinedTextField(
// value = "",
// onValueChange = { },
// placeholder = { Text("Поиск товара") },
// modifier = Modifier
// .fillMaxWidth()
// .padding(8.dp)
// ) }
item {Text(
text = "Товары:",
fontSize = 28.sp,
color = Color.Black,
modifier = Modifier.padding(8.dp)
)}
item{
Button(
onClick = {navController.navigate("addProduct")},
colors = ButtonDefaults.buttonColors(
containerColor= Color.Transparent,
contentColor = myColor3
),
contentPadding = PaddingValues(0.dp),
modifier = Modifier.size(30.dp).padding(top = 10.dp)
item{products.forEach {
createProductCard(it.name, it.price, it.img, { navController.navigate("product/" + it.productId) } )
) {
Image(
painter = painterResource(id = R.drawable.add),
contentDescription = null,
modifier = Modifier.size(50.dp)
)
}
}
item {
Text(
text = "Товары:",
fontSize = 28.sp,
color = Color.Black,
modifier = Modifier.padding(8.dp)
)
}
// item {
// products.forEach {
// createProductCard(
// it.name,
// it.price,
// it.img,
// { navController.navigate("product/" + it.productId) })
// }
// }
items (
count = products.itemCount,
key = products.itemKey { product -> product.productId!! }
) { index ->
val it = products[index]!!
createProductCard( it.name, it.price, it.img, { navController.navigate("product/" + it.productId)}){
coroutineScope.launch {
kotlin.runCatching {
userViewModel.addProductCart(UserProductCart(userViewModel.getUserId(), it.productId!!))
}
.onSuccess {
val toast = Toast.makeText(context, "Добавлено", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
}
.onFailure {
val toast = Toast.makeText(context, "Уже есть в корзине", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
}
}
}
}
}
}
}

View File

@@ -1,7 +1,9 @@
package com.example.myapplication.components
import ButtonNice
import Input
import TextNice
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -12,57 +14,68 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.components.common.myInput
import com.example.myapplication.database.entities.User
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import myColor1
import myColor2
import myColor3
@Composable
fun Registration (navController: NavController){
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 10.dp, end = 10.dp),
fun Registration(
navController: NavController,
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val mailState = remember { mutableStateOf("") }
val loginState = remember { mutableStateOf("") }
val passState = remember { mutableStateOf("") }
val repeatPassState = remember { mutableStateOf("") }
Column(modifier = Modifier
.fillMaxSize()
.padding(start = 10.dp, end = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
Input("Имя")
Input("Эл.почта")
Input("Пароль")
Input("Повторите пароль")
Button(
onClick = { /*TODO*/ },
modifier = Modifier.fillMaxWidth(),
)
{
TextNice("Создать аккаунт")
}
Button(
onClick = {navController.navigate("authorization")},
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Black),
colors= ButtonDefaults.buttonColors(
containerColor= Color.White,
contentColor = Color.Gray
))
{
TextNice("Авторизация")
) {
TextNice(text = "Регистрация")
myInput("Email", mailState)
myInput("Логин", loginState)
myInput("Пароль", passState)
myInput("Повторите Пароль", repeatPassState)
ButtonNice(text = "Регистрация", color = myColor1){
coroutineScope.launch {
if(passState.value != repeatPassState.value) {
val toast = Toast.makeText(context, "Пароли не совпадают", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
return@launch
}
userViewModel.insert(User(null, mailState.value, loginState.value, passState.value))
navController.navigate("authorization")
}
}
ButtonNice(text = "Авторизация", color = myColor2, onClickAction = {navController.navigate("authorization")})
}
}
@Preview(showBackground = true)
@Composable
fun RegistrationPreview() {
val navController = rememberNavController()
Registration(navController = navController)
}

View File

@@ -1,100 +0,0 @@
package com.example.myapplication.components
import ButtonNice
import TextNice
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.R
import myColor2
import myColor3
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun addProduct(navController: NavController) {
Column(
modifier = Modifier
.fillMaxSize().padding(15.dp)
)
{
Box(
modifier = Modifier.padding(bottom = 10.dp).fillMaxWidth(),
contentAlignment = Alignment.Center
) {
TextNice("Добавление товара")
}
OutlinedTextField(
value = "",
onValueChange = { },
placeholder = { Text("Название товара") },
modifier = Modifier
.fillMaxWidth()
.padding(top = 5.dp, bottom = 5.dp)
)
OutlinedTextField(
value = "",
onValueChange = { },
placeholder = { Text("Описание товара") },
modifier = Modifier
.fillMaxWidth()
.padding(top = 5.dp, bottom = 5.dp)
.height(200.dp)
)
OutlinedTextField(
value = "",
onValueChange = { },
placeholder = { Text("Цена") },
modifier = Modifier
.fillMaxWidth()
.padding(top = 5.dp, bottom = 20.dp)
)
Button(
onClick = {navController.navigate("addProduct")},
colors = ButtonDefaults.buttonColors(
containerColor= Color.Transparent,
contentColor = myColor3
),
contentPadding = PaddingValues(0.dp),
modifier = Modifier.size(50.dp).padding(top = 10.dp)
) {
Image(
painter = painterResource(id = R.drawable.addphoto),
contentDescription = null,
modifier = Modifier.size(50.dp)
)
}
ButtonNice(
text = "Добавить",
color = myColor2,
onClickAction = { navController.navigate("category") })
ButtonNice(
text = "Отмена",
color = Color.White,
onClickAction = { navController.navigate("category") })
}
}

View File

@@ -24,6 +24,7 @@ fun ButtonNice(text: String, color: Color, onClickAction: () -> Unit = {}){
shape = RoundedCornerShape(corner = CornerSize(5.dp)),
colors = ButtonDefaults.buttonColors(
containerColor = color,
contentColor = Color.White
)
) {
Text(text, fontSize = 20.sp, color = Color.Black)

View File

@@ -0,0 +1,64 @@
package com.example.myapplication.components.common
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun myInput(label: String, text: MutableState<String>, height: Dp = 50.dp, modifier: Modifier = Modifier) {
Column(modifier=modifier) {
Text(label)
TextField(
value = text.value,
onValueChange = {newText -> text.value = newText},
colors= TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
errorBorderColor = Color.Transparent
),
modifier = Modifier
.border(1.dp, Color.Black, RoundedCornerShape(5.dp))
.fillMaxWidth()
.height(height),
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun passwordInput(label: String, text: MutableState<String>, height: Dp = 50.dp, modifier: Modifier = Modifier) {
Column(modifier=modifier) {
Text(label)
TextField(
value = text.value,
onValueChange = {newText -> text.value = newText},
colors= TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
errorBorderColor = Color.Transparent
),
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier
.border(1.dp, Color.Black, RoundedCornerShape(5.dp))
.fillMaxWidth()
.height(height),
)
}
}

View File

@@ -32,7 +32,7 @@ import myColor3
import myColor4
@Composable
fun createProductCard(name: String, price: Double, img: Bitmap, onClickAction: () -> Unit){
fun createProductCard(name: String, price: Double, img: Bitmap, onClickAction: () -> Unit, onClickAction2: () -> Unit){
Column () {
Row {
Image(
@@ -47,7 +47,7 @@ fun createProductCard(name: String, price: Double, img: Bitmap, onClickAction: (
ButtonNice("Инфо: " , Color.White, onClickAction)
}
}
ButtonNice("Добавить в корзину" , myColor1, onClickAction)
ButtonNice("Добавить в корзину" , myColor1, onClickAction2)
}
}
@Composable
@@ -64,7 +64,7 @@ fun ProductCardInCart(name: String, price: Double, img: Bitmap, onClickAction: (
Text(text = price.toString() + "", fontSize = 16.sp, color = Color.Black)
Button(
onClick = { onClickAction },
onClick = onClickAction,
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = myColor3

View File

@@ -31,7 +31,7 @@ fun CatalogItems (navController: NavController, title: String, products: Mutable
}
item{
for (product in products){
createProductCard(product.name, product.price, product.img, { })
createProductCard(product.name, product.price, product.img, { }, { })
}
}
item {

View File

@@ -0,0 +1,69 @@
package com.example.myapplication.components.templates
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.myapplication.database.entities.Category
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DropDown(items: List<Category>, selected: MutableState<Category?>) {
val expanded = remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded.value,
onExpandedChange = {
expanded.value = !expanded.value
}
) {
TextField(
value = when(selected.value) {
null -> "Значение не выбрано"
else -> selected.value!!.name
},
onValueChange = {},
readOnly = true,
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded.value)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
)
DropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false },
modifier = Modifier
.background(Color.White)
.exposedDropdownSize()
) {
items.forEach {
DropdownMenuItem(
text = {
Text(text = it.name)
},
onClick = {
selected.value = it
expanded.value = false
}
)
}
}
}
}

View File

@@ -38,6 +38,7 @@ fun ProductCard(product: ProductOld, onClickAction: () -> Unit) {
ButtonNice(text ="Добавить в корзину" , color = myColor1, onClickAction)
}
}
@Preview(showBackground = true)
@Composable
fun ProductCardPreview() {

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,42 +40,17 @@ 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{
CategoryItem(it.name, { navController.navigate("main/" + it.id)})
}
}
}
@Composable
fun myFun(categoryId: Int, title: String){
val navController = rememberNavController()
val context = LocalContext.current
val products = remember { mutableStateListOf<Product>() }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
AppDb.getInstance(context).categoryDao().getProductsByCategory(categoryId).collect { data ->
products.clear()
data.forEach {
products.add(it)
}
}
}
}
CatalogItems(navController, title, products )
}

View File

@@ -0,0 +1,31 @@
package com.example.myapplication.database
import android.content.Context
import com.example.myapplication.database.repository.CategoryRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
import com.example.myapplication.database.repository.OfflineProductRepository
import com.example.myapplication.database.repository.OfflineUserRepository
import com.example.myapplication.database.repository.ProductRepository
import com.example.myapplication.database.repository.UserRepository
interface AppContainer {
val userRepository: UserRepository
val productRepository: ProductRepository
val categoryRepository: CategoryRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDb.getInstance(context).userDao())
}
override val productRepository: ProductRepository by lazy {
OfflineProductRepository(AppDb.getInstance(context).productDao())
}
override val categoryRepository: CategoryRepository by lazy {
OfflineCategoryRepository(AppDb.getInstance(context).categoryDao())
}
companion object {
const val TIMEOUT = 5000L
}
}

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"
@@ -39,67 +43,67 @@ abstract class AppDb: RoomDatabase(){
val categoryDao = database.categoryDao()
categoryDao.insert(Category(1, "Видеокарты"))
categoryDao.insert(Category(2, "Процессоры"))
categoryDao.insert(Category(3, "Оперативная память"))
categoryDao.insert(Category(4, "Твердотельные накопители"))
// categoryDao.insert(Category(1, "Видеокарты"))
// categoryDao.insert(Category(2, "Процессоры"))
// categoryDao.insert(Category(3, "Оперативная память"))
// categoryDao.insert(Category(4, "Твердотельные накопители"))
val productDao = database.productDao()
val img1: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product1)
val img2: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product2)
val img3: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product3)
val img4: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product4)
val img5: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product5)
val img6: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product6)
val img7: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product7)
val img8: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product8)
// val img2: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product2)
// val img3: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product3)
// val img4: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product4)
// val img5: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product5)
// val img6: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product6)
// val img7: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product7)
// val img8: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.product8)
productDao.insert(Product(1, "MSI GeForce RTX 4090 VENTUS 3X OC",
"Информацио о товаре MSI GeForce RTX 4090 VENTUS 3X OC ",
210999.0, img1, 1
))
//
// productDao.insert(Product(2, "Palit GeForce GTX 1660 SUPER",
// "Информацио о товаре Palit GeForce GTX 1660 SUPER ",
// 25999.0, img2, 1
// ))
//
// productDao.insert(Product(3, "Intel Celeron G5905 OEM",
// "Информацио о товаре Intel Celeron G5905 OEM ",
// 25999.0, img3, 2
// ))
//
//
// productDao.insert(Product(4, "AMD Ryzen 5 4500 BOX",
// "Информацио о товаре Intel Celeron G5905 OEM",
// 9799.0, img4, 2
// ))
//
// productDao.insert(Product(5, "Kingston FURY Beast Black",
// "Информацио о товаре Kingston FURY Beast Black",
// 4499.0, img5, 3
// ))
//
// productDao.insert(Product(6, "ADATA XPG SPECTRIX D41 RGB",
// "Информацио о товаре ADATA XPG SPECTRIX D41 RGB",
// 4599.0, img6, 3
// ))
//
// productDao.insert(Product(7, "ADATA SU650",
// "Информацио о товаре ADATA SU650",
// 1550.0, img7, 4
// ))
// productDao.insert(Product(8, "Smartbuy Revival 3",
// "Информацио о товаре Smartbuy Revival 3",
// 1250.0, img8, 4
// ))
productDao.insert(Product(2, "Palit GeForce GTX 1660 SUPER",
"Информацио о товаре Palit GeForce GTX 1660 SUPER ",
25999.0, img2, 1
))
productDao.insert(Product(3, "Intel Celeron G5905 OEM",
"Информацио о товаре Intel Celeron G5905 OEM ",
25999.0, img3, 2
))
productDao.insert(Product(4, "AMD Ryzen 5 4500 BOX",
"Информацио о товаре Intel Celeron G5905 OEM",
9799.0, img4, 2
))
productDao.insert(Product(5, "Kingston FURY Beast Black",
"Информацио о товаре Kingston FURY Beast Black",
4499.0, img5, 3
))
productDao.insert(Product(6, "ADATA XPG SPECTRIX D41 RGB",
"Информацио о товаре ADATA XPG SPECTRIX D41 RGB",
4599.0, img6, 3
))
productDao.insert(Product(7, "ADATA SU650",
"Информацио о товаре ADATA SU650",
1550.0, img7, 4
))
productDao.insert(Product(8, "Smartbuy Revival 3",
"Информацио о товаре Smartbuy Revival 3",
1250.0, img8, 4
))
val userDao = database.userDao()
userDao.insert(User(1, "Иванов И.И", "ivanov"))
database.userDao().addProductCart(UserProductCart(1, 1))
database.userDao().addProductCart(UserProductCart(1, 3))
//database.userDao().addProductCart(UserProductCart(1, 2))
val userDao = database.userDao()
userDao.insert(User(5, "Иванов И.И", "ivanov","ivanov"))
// database.userDao().addProductCart(UserProductCart(1, 1))
// database.userDao().addProductCart(UserProductCart(1, 3))
// database.userDao().addProductCart(UserProductCart(1, 2))
}
}
fun getInstance(appContext: Context): AppDb {

View File

@@ -10,10 +10,8 @@ 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)
@Query("select * from products where category_id = :categoryId")
fun getProductsByCategory(categoryId: Int): Flow<List<Product>>
}

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,11 +11,17 @@ 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): User
@Query("select * from users where users.login = :login and users.password = :password")
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)

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

@@ -11,7 +11,9 @@ data class User(
@ColumnInfo(name="name")
val name: String,
@ColumnInfo(name="login")
val login: String
val login: String,
@ColumnInfo(name="password")
val password: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true

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

@@ -0,0 +1,9 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.dao.CategoryDao
import com.example.myapplication.database.entities.Category
class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository {
override suspend fun getAll(): List<Category> = categoryDao.getAll()
override suspend fun insert(category: Category) = categoryDao.insert(category)
}

View File

@@ -0,0 +1,44 @@
package com.example.myapplication.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.database.dao.ProductDao
import com.example.myapplication.database.entities.Product
import kotlinx.coroutines.flow.Flow
class OfflineProductRepository(private val productDao: ProductDao) : ProductRepository {
override fun getAll(): Flow<PagingData<Product>> {
return Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
pagingSourceFactory = {
productDao.getAll()
}
).flow
}
override fun getById(id: Int): Flow<Product> = productDao.getById(id)
override fun getByCategory(category_id: Int): Flow<PagingData<Product>> {
return Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
pagingSourceFactory = {
productDao.getByCategory(category_id)
}
).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

@@ -0,0 +1,19 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.dao.UserDao
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserProductCart
import com.example.myapplication.database.entities.UserWithCartProduct
import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
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(userProduct: UserProductCart) = userDao.addProductCart(userProduct)
}

View File

@@ -1,4 +1,14 @@
package com.example.myapplication.database.repository
import androidx.paging.PagingData
import com.example.myapplication.database.entities.Product
import kotlinx.coroutines.flow.Flow
interface ProductRepository {
fun getAll(): Flow<PagingData<Product>>
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

@@ -1,16 +1,16 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserProductCart
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 getUserItemsCartById(id: Int): Flow<UserWithCartItems>
suspend fun deleteCartItem(userId: Int, itemId: Int)
suspend fun deleteFavoriteItem(userId: Int, itemId: Int)
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 addItemCart(userItem: UserItemCart)
suspend fun addProductCart(userItem: UserProductCart)
}

View File

@@ -26,7 +26,7 @@ import com.example.myapplication.components.Authorization
import com.example.myapplication.components.Cart
import com.example.myapplication.components.Main
import com.example.myapplication.components.Registration
import com.example.myapplication.components.myFun
import com.example.myapplication.components.AddProduct
import com.example.myapplication.components.templates.ProductForPage
import com.example.myapplication.components.Сategory
import com.example.myapplication.ui.theme.MyApplicationTheme
@@ -96,8 +96,8 @@ fun Navbar() {
Main(navController, it.getInt("id"))
}
}
composable("category") { Сategory(navController) }
composable("addProduct") { AddProduct(navController) }
composable("cart") { Cart(navController) }
composable(
@@ -106,6 +106,7 @@ fun Navbar() {
) { backStackEntry ->
backStackEntry.arguments?.let { ProductForPage(it.getInt("id")) }
}
}
}
}

View File

@@ -0,0 +1,32 @@
package com.example.myapplication.viewModels
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 kotlinx.coroutines.flow.Flow
class CategoryViewModel (
private val categoryRepository: CategoryRepository
): ViewModel() {
companion object {
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val app: MyApp = (checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) as MyApp)
return CategoryViewModel(RestCategoryRepository(MyServerService.getInstance(), app.categoryRepository)) as T
}
}
}
suspend fun getAll(): List<Category> {
return categoryRepository.getAll()
}
}

View File

@@ -0,0 +1,60 @@
package com.example.myapplication.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
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
class ProductViewModel(
private val productRepository: ProductRepository
) : ViewModel() {
companion object {
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory{
override fun <T: ViewModel> create(
modelClass: Class<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
}
}
}
fun getAll() : Flow<PagingData<Product>> {
return productRepository.getAll().cachedIn(viewModelScope)
}
fun getById(id: Int) : Flow<Product> {
return productRepository.getById(id)
}
fun getByCategory(cId: Int): Flow<PagingData<Product>> {
return productRepository.getByCategory(cId).cachedIn(viewModelScope)
}
suspend fun insert(product: Product) {
productRepository.insert(product)
}
}

View File

@@ -0,0 +1,65 @@
package com.example.myapplication.viewModels
import androidx.lifecycle.ViewModel
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
import com.example.myapplication.database.repository.OfflineUserRepository
import com.example.myapplication.database.repository.UserRepository
import kotlinx.coroutines.flow.Flow
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
companion object {
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory{
override fun <T: ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
) : T {
return UserViewModel(UserRestRepository(MyServerService.getInstance())) as T
}
}
}
fun getUserId(): Int {
return GlobalUser.getInstance().userId
}
fun setUserId(id: Int) {
GlobalUser.getInstance().userId = id
}
suspend fun getAll(): List<User> {
return userRepository.getAll()
}
suspend fun getById(id: Int): User {
return userRepository.getById(id)
}
suspend fun getByAuth(login: String, password: String): User? {
return userRepository.getByAuth(login, password)
}
suspend fun deleteCartProduct(userId: Int, productId: Int) {
userRepository.deleteCartProduct(userId, productId)
}
suspend fun getUserProductsCartById(id: Int): UserWithCartProduct {
return userRepository.getUserProductCartById(id)
}
suspend fun insert(user: User) {
userRepository.insert(user)
}
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
}