15 Commits
main ... 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
89fee0d2c6 4 2023-12-18 13:45:37 +04:00
56b1e815c6 Пофиксил всо 2023-12-18 00:29:29 +04:00
1381fe1a37 Пофиксил 2023-12-14 02:17:19 +04:00
773046a752 end 2023-11-24 21:48:37 +04:00
861c47ab9b 24.11 2023-11-24 12:19:09 +04:00
9fd9110d90 12.11 2023-11-12 20:58:46 +04:00
27064d323b com2 2023-11-11 03:13:55 +04:00
98e60a6fb0 com2 2023-11-08 23:22:54 +04:00
39e8794d64 com2 2023-11-08 23:20:02 +04:00
106 changed files with 3613 additions and 0 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
My Application

123
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

17
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\romai\.android\avd\Pixel_3a_API_34_extension_level_7_x86_64.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-12-13T21:04:15.986503500Z" />
</component>
</project>

20
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -0,0 +1,41 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.20" />
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

91
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,91 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "com.example.myapplication"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.5"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.0")
implementation("androidx.navigation:navigation-compose:2.6.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
// Room
val room_version = "2.5.2"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
ksp("androidx.room:room-compiler:$room_version")
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")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
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")
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,24 @@
package com.example.myapplication
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.myapplication", appContext.packageName)
}
}

View File

@@ -0,0 +1,32 @@
<?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"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyApplication">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

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,36 @@
package com.example.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplication.navigation.Navbar
import com.example.myapplication.ui.theme.MyApplicationTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Navbar()
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MyApplicationTheme {
Navbar()
}
}

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

@@ -0,0 +1,78 @@
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
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
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, userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)){
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;
data.products.forEach {
sumPrice.value += it.price
products.add(it)
}
}
}
LazyColumn (contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)){
item {
TextNice("Корзина")
}
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)
}
}
}
}
item {
ButtonNice(text = "Оплатить: " + sumPrice.value.toString() + "Р", color = myColor4)
}
}
}

View File

@@ -0,0 +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.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 androidx.paging.compose.itemKey
import com.example.myapplication.R
import com.example.myapplication.components.funs.createProductCard
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,
catalogId: Int,
productViewModel: ProductViewModel = viewModel(factory = ProductViewModel.factory),
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
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{
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)
) {
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

@@ -0,0 +1,81 @@
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
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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,
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
) {
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")})
}
}

View File

@@ -0,0 +1,32 @@
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
@Composable
fun ButtonNice(text: String, color: Color, onClickAction: () -> Unit = {}){
Button(
onClick = onClickAction,
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Black),
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,38 @@
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.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Input(label: String, height: Dp = 50.dp, modifier: Modifier = Modifier.padding(5.dp)) {
Column(modifier=modifier) {
OutlinedTextField(
placeholder = {
Text(label, fontSize = 15.sp, color = Color.Black)
},
value = "",
onValueChange = {},
colors= TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
errorBorderColor = Color.Transparent
),
modifier = Modifier.border(1.dp, Color.Black, RoundedCornerShape(10.dp)).fillMaxWidth().height(height),
)
}
}

View File

@@ -0,0 +1,10 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
@Composable
fun TextNice(text: String) {
Text(text, fontSize = 25.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}

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

@@ -0,0 +1,120 @@
package com.example.myapplication.components.funs
import ButtonNice
import TextNice
import android.graphics.Bitmap
import androidx.annotation.DrawableRes
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.Row
import androidx.compose.foundation.layout.aspectRatio
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.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
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.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.R
import myColor1
import myColor3
import myColor4
@Composable
fun createProductCard(name: String, price: Double, img: Bitmap, onClickAction: () -> Unit, onClickAction2: () -> Unit){
Column () {
Row {
Image(
bitmap = img.asImageBitmap(),
contentDescription = null,
modifier = Modifier.size(150.dp)
)
Column (modifier = Modifier.padding(start = 15.dp, end = 15.dp)){
Text(name, fontSize = 24.sp, color = Color.Black)
Text(price.toString() + "", fontSize = 16.sp, color = Color.Black)
ButtonNice("Инфо: " , Color.White, onClickAction)
}
}
ButtonNice("Добавить в корзину" , myColor1, onClickAction2)
}
}
@Composable
fun ProductCardInCart(name: String, price: Double, img: Bitmap, onClickAction: () -> Unit) {
Column(modifier = Modifier.padding(top = 20.dp, start = 10.dp, end = 10.dp)) {
Row {
Image(
bitmap = img.asImageBitmap(),
contentDescription = null,
modifier = Modifier.size(150.dp)
)
Column(modifier = Modifier.padding(start = 15.dp, end = 15.dp)) {
Text(text = name, fontSize = 24.sp, color = Color.Black)
Text(text = price.toString() + "", fontSize = 16.sp, color = Color.Black)
Button(
onClick = onClickAction,
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.delete),
contentDescription = null,
modifier = Modifier.size(50.dp)
)
}
}
}
}
@Composable
fun createProductPage(
name: String,
info: String,
price: Int,
@DrawableRes imgId: Int,
onClickAction: () -> Unit
) {
LazyColumn(modifier = Modifier.fillMaxSize().padding(10.dp)) {
item {
Box(
modifier = Modifier.padding(bottom = 10.dp).fillMaxWidth(),
contentAlignment = Alignment.Center
) {
TextNice(name)
}
}
item {
Image(
painter = painterResource(id = imgId),
contentDescription = null,
modifier = Modifier
.size(400.dp)
.aspectRatio(1f)
)
}
item { TextNice("Характеристики: ") }
item { Text(info) }
item { ButtonNice("Добавить в корзину", myColor4, onClickAction) }
}
}
}

View File

@@ -0,0 +1,10 @@
package com.example.myapplication.components.templates
import ButtonNice
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
@Composable
fun CategoryItem (text: String, onClickAction: () -> Unit){
ButtonNice(text, color = Color.White, onClickAction )
}

View File

@@ -0,0 +1,56 @@
package com.example.myapplication.components.templates
import TextNice
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.components.funs.createProductCard
import com.example.myapplication.database.entities.Product
import com.example.myapplication.product.ProductOld
import myColor3
@Composable
fun CatalogItems (navController: NavController, title: String, products: MutableList<Product>){
LazyColumn (
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
){
item {
TextNice(title)
}
item{
for (product in products){
createProductCard(product.name, product.price, product.img, { }, { })
}
}
item {
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.add),
contentDescription = null,
modifier = Modifier.size(50.dp)
)
}
}
}
}

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

@@ -0,0 +1,48 @@
package com.example.myapplication.components.templates
import ButtonNice
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.compose.ui.unit.sp
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.product.ProductOld
import myColor1
@Composable
fun ProductCard(product: ProductOld, onClickAction: () -> Unit) {
Column (modifier = Modifier.padding(top = 20.dp, start = 10.dp, end=10.dp)) {
Row {
Image(
painter = painterResource(id = product.imgId),
contentDescription = null,
modifier = Modifier
.size(200.dp)
.aspectRatio(1f) // Устанавливаем соотношение сторон 1:1 (квадрат)
)
Column (modifier = Modifier.padding(start = 15.dp, end = 15.dp)){
Text(text = product.name, fontSize = 24.sp, color = Color.Black)
Text(text = product.price.toString() + "", fontSize = 16.sp, color = Color.Black)
}
}
ButtonNice(text ="Добавить в корзину" , color = myColor1, onClickAction)
}
}
@Preview(showBackground = true)
@Composable
fun ProductCardPreview() {
val navController = rememberNavController()
//val product: Product = Product("MSI GeForce RTX 4090 VENTUS 3X OC" ,"Инфо",15000, R.drawable.product1)
//ProductCard(product, {navController.navigate("main")})
}

View File

@@ -0,0 +1,52 @@
package com.example.myapplication.components.templates
import TextNice
import androidx.annotation.DrawableRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
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.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun ProductCardInCart(name: String, price: Int, @DrawableRes imgId: Int) {
Column (modifier = Modifier.padding(top = 20.dp, start = 10.dp, end=10.dp)) {
Row {
Image(
painter = painterResource(id = imgId),
contentDescription = null,
modifier = Modifier
.size(200.dp)
.aspectRatio(1f)
)
Column (modifier = Modifier.padding(start = 15.dp, end = 15.dp)){
Text(text = name, fontSize = 24.sp, color = Color.Black)
Text(text = price.toString() + "", fontSize = 16.sp, color = Color.Black)
}
}
Button(
onClick = { },
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Black),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
)
) {
TextNice("Убрать из корзины")
}
}
}

View File

@@ -0,0 +1,30 @@
package com.example.myapplication.components.templates
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Product
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun ProductForPage(id: Int) {
val context = LocalContext.current
val (product, setProduct) = remember { mutableStateOf<Product?>(null) } //спросить
LaunchedEffect(Unit) {
withContext(Dispatchers.IO){
AppDb.getInstance(context).productDao().getById(id).collect {
setProduct(it)
}
}
}
if (product != null){
ProductPage(product!!.name, product!!.info, product!!.price, product!!.img, { } )
}
}

View File

@@ -0,0 +1,49 @@
package com.example.myapplication.components.templates
import ButtonNice
import TextNice
import android.graphics.Bitmap
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
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 myColor4
@Composable
fun ProductPage(name: String, info: String, price: Double, img: Bitmap, onClickAction: () -> Unit){
LazyColumn (modifier = Modifier.fillMaxSize().padding(10.dp)) {
item{
TextNice(name)
}
item {
Image(
bitmap = img.asImageBitmap(),
contentDescription = null,
modifier = Modifier.size(150.dp)
)
}
item{TextNice("Характеристики: ")}
item{Text(info)}
item{ButtonNice("Добавить в корзину", myColor4, onClickAction )}
}
}

View File

@@ -0,0 +1,56 @@
package com.example.myapplication.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
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
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,
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
) {
val context = LocalContext.current
val categories = remember { mutableStateListOf<Category>() }
LaunchedEffect(Unit) {
if(userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
withContext(Dispatchers.IO) {
categories.addAll(categoryViewModel.getAll())
}
}
categories.forEach{
CategoryItem(it.name, { navController.navigate("main/" + it.id)})
}
}
}

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

@@ -0,0 +1,129 @@
package com.example.myapplication.database
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
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
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@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"
@Volatile
private var INSTANCE: AppDb? = null
private suspend fun populateDatabase(context: Context) {
INSTANCE?.let { database ->
val categoryDao = database.categoryDao()
// 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)
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
// ))
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 {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
AppDb::class.java,
DB_NAME
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
populateDatabase(appContext)
}
}
})
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@@ -0,0 +1,17 @@
package com.example.myapplication.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.entities.Product
import kotlinx.coroutines.flow.Flow
@Dao
interface CategoryDao {
@Query("select * from categories")
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

@@ -0,0 +1,33 @@
package com.example.myapplication.database.dao;
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
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
@Dao
interface UserDao {
@Query("select * from users")
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)
@Query("select * from users where users.userId = :id")
fun getUserProductCartById(id: Int): UserWithCartProduct
@Insert
suspend fun addProductCart(userProduct: UserProductCart)
}

View File

@@ -0,0 +1,26 @@
package com.example.myapplication.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "categories")
data class Category(
@PrimaryKey(autoGenerate = true)
var id: Int?,
@ColumnInfo(name="name")
val name: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Category
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@@ -0,0 +1,19 @@
package com.example.myapplication.database.entities
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.TypeConverter
import java.io.ByteArrayOutputStream
class Converters {
@TypeConverter
fun fromBitmap(bitmap: Bitmap) : ByteArray {
val outputStream = ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
}
@TypeConverter
fun toBitmap(byteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}

View File

@@ -0,0 +1,59 @@
package com.example.myapplication.database.entities
import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
tableName = "products",
foreignKeys = [
ForeignKey(
entity = Category::class,
parentColumns = ["id"],
childColumns = ["category_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class Product(
@PrimaryKey(autoGenerate = true)
var productId: Int?,
@ColumnInfo(name="name")
val name: String,
@ColumnInfo(name="info")
val info: String,
@ColumnInfo(name="price")
val price: Double,
@ColumnInfo(name="img")
val img: Bitmap,
@ColumnInfo(name="category_id")
val categoryId: Int
){
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Product
if (productId != other.productId) return false
return true
}
override fun hashCode(): Int {
return productId ?: -1
}
}

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

@@ -0,0 +1,29 @@
package com.example.myapplication.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
var userId: Int?,
@ColumnInfo(name="name")
val name: String,
@ColumnInfo(name="login")
val login: String,
@ColumnInfo(name="password")
val password: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as User
if (userId != other.userId) return false
return true
}
override fun hashCode(): Int {
return userId ?: -1
}
}

View File

@@ -0,0 +1,15 @@
package com.example.myapplication.database.entities
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class UserWithCartProduct (
@Embedded val user : User,
@Relation(
parentColumn = "userId",
entityColumn = "productId",
associateBy = Junction(UserProductCart::class)
)
val products : List<Product>
)

View File

@@ -0,0 +1,9 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.entities.Category
import kotlinx.coroutines.flow.Flow
interface CategoryRepository {
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

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

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

@@ -0,0 +1,11 @@
package com.example.myapplication.navigation;
import androidx.annotation.DrawableRes;
class NavItem(
val route : String,
val label : String,
@DrawableRes val icon : Int
) {
}

View File

@@ -0,0 +1,123 @@
package com.example.myapplication.navigation
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.myapplication.R
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.AddProduct
import com.example.myapplication.components.templates.ProductForPage
import com.example.myapplication.components.Сategory
import com.example.myapplication.ui.theme.MyApplicationTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Navbar() {
val navController = rememberNavController()
val items = listOf(
NavItem("main/0", "Главная", R.drawable.home),
NavItem("category", "Каталог", R.drawable.search),
NavItem("cart", "Корзина", R.drawable.cart),
)
// val videocars = listOf(
// Product(3, "MSI GeForce RTX 4090 VENTUS 3X OC", "Видеокарта MSI GeForce RTX 4090 VENTUS 3X OC создана для игровых ПК и профессиональных рабочих станций. Благодаря архитектуре NVIDIA Ada Lovelace она обеспечивает мощный вычислительный потенциал и плавность отображения динамичной графики без задержек. Тактовая частота процессора составляет 2230 МГц и способна увеличиваться до 2565 МГц при разгоне. Видеокарта оснащена 24 ГБ выделенной памяти стандарта GDDR6X.\n" +
// "Вывод изображения на внешние мониторы может выполняться посредством 3 разъемов DisplayPort и 1 HDMI. Три вентилятора совместно с радиатором и тепловыми трубками быстро рассеивают тепло и поддерживают низкую температуру нагрева. Усиленная подсистема питания и отборные компоненты гарантируют стабильность работы MSI GeForce RTX 4090 VENTUS 3X ОС. Защитная пластина на тыловой стороне делает видеокарту устойчивой к деформации и механическим воздействиям. Фирменное приложение MSI Center позволяет выполнять мониторинг и настраивать параметры графического адаптера.", 210999, R.drawable.product1),
// Product(4, "Palit GeForce GTX 1660 SUPER", "Видеокарта Palit GeForce GTX 1660 SUPER Gaming Pro [NE6166S018J9-1160A-1] представляет собой производительное решение в компактном корпусе, которое станет отличным выбором для компьютерных систем в миниатюрном корпусе. В основе графического ускорителя используется многоядерный процессор, работающий в широком частотном диапазоне, что вкупе с большим объемом встроенной памяти может обеспечить комфортную работу практически с любыми задачами. Максимальная температура ускорителя при этом может достигать отметки 93°C, для отвода тепла используется несколько осевых вентиляторов.\n" +
// "Графический ускоритель Palit GeForce GTX 1660 SUPER Gaming Pro также отличается строгим дизайном, благодаря чему легко сможет дополнить собой практически любую сборку. Длина данной модели не превышает 235 мм, а толщина 42 мм, благодаря чему для установки задействуется всего 2 отсека расширения. Для подключения к материнской плате используется интерфейс PCI-E 3.0. Для внешних мониторов на корпусе также предусмотрено несколько видов видеоразъемов.", 25999, R.drawable.product2),
// )
// val processors = listOf(
// Product(1, "Intel Celeron G5905 OEM", "Процессор Intel Celeron G5905 представляет собой 2-ядерный чипсет начального уровня, подходящий для сборки домашнего или офисного компьютера. Созданная на базе архитектуры Intel Comet Lake-S модель использует 14-нанометровый техпроцесс, благодаря которому обеспечивается оптимальное сочетание производительности и энергопотребления. Для установки чипсета на материнскую плату используется популярный сокет LGA 1200. В работе устройство использует 2 производительных ядра, способных одновременно обрабатывать два вычислительных потока.\n" +
// "Процессор Intel Celeron G5905 функционирует на фиксированной тактовой частоте 3.5 ГГц. В данной модели предусмотрено интегрированное графическое ядро Intel UHD Graphics 610, которому под силу справиться с обработкой нересурсоемкой графики и ее выводом на экран монитора.", 4099, R.drawable.product3),
// Product(2, "AMD Ryzen 5 4500 BOX", "Шестиядерный процессор AMD Ryzen 5 4500 BOX основан на архитектуре Zen 2 и выполнен по техпроцессу TSMC 7FF. Устройство имеет базовую тактовую частоту 3.6 ГГц и максимальную 4.1 ГГц. Он также поддерживает технологии Simultaneous Multithreading (SMT) и Precision Boost, позволяющие эффективно использовать все 6 ядер процессора.\n" +
// "AMD Ryzen 5 4500 BOX обладает высокими показателями производительности, особенно в многозадачных сценариях. Он также имеет низкое потребление энергии, что позволяет создавать энергоэффективные системы. Процессор поддерживает стандартную сокетную платформу AM4 и может быть установлен на совместимые материнские платы.", 9799, R.drawable.product4),
// )
Scaffold(
bottomBar = {
NavigationBar {
items.forEach { item ->
NavigationBarItem(
icon = {
Image(
painter = painterResource(item.icon),
contentDescription = null,
modifier = Modifier.size(35.dp)
)
},
label = { Text(item.label) },
onClick = {
navController.navigate(item.route)
},
selected = false,
modifier = Modifier.fillMaxSize()
)
}
}
}
)
{ innerPaddings ->
NavHost(
navController = navController,
startDestination = "authorization",
modifier = Modifier.padding(innerPaddings)
) {
composable("authorization") { Authorization(navController) }
composable("registration") { Registration(navController) }
//composable("main") { Main(navController) }
composable(
"main/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let {
Main(navController, it.getInt("id"))
}
}
composable("category") { Сategory(navController) }
composable("addProduct") { AddProduct(navController) }
composable("cart") { Cart(navController) }
composable(
"product/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { ProductForPage(it.getInt("id")) }
}
}
}
}
@Preview(name="Navbar")
@Composable
fun PreviewNavbar() {
MyApplicationTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Navbar()
}
}
}

View File

@@ -0,0 +1,6 @@
import androidx.compose.ui.graphics.Color
val myColor1 = Color(0, 153, 153, 255)
val myColor2 = Color(255, 170, 0, 255)
val myColor3 = Color(103, 58, 183, 255)
val myColor4 = Color(185, 20, 255, 255)

View File

@@ -0,0 +1,11 @@
package com.example.myapplication.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@@ -0,0 +1,70 @@
package com.example.myapplication.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MyApplicationTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,34 @@
package com.example.myapplication.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

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,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">My Application</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MyApplication" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

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

@@ -0,0 +1,17 @@
package com.example.myapplication
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

7
build.gradle.kts Normal file
View File

@@ -0,0 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
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
}

Some files were not shown because too many files have changed in this diff Show More