initCommit
48
.gitignore
vendored
@ -1,35 +1,15 @@
|
||||
# ---> Android
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
output.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
3
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
||||
My Application
|
123
.idea/codeStyles/Project.xml
Normal 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
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
6
.idea/compiler.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
20
.idea/gradle.xml
Normal 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>
|
41
.idea/inspectionProfiles/Project_Default.xml
Normal 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
Normal 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
Normal 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>
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
90
app/build.gradle.kts
Normal file
@ -0,0 +1,90 @@
|
||||
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 = 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.myapplication"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
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.2")
|
||||
|
||||
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
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
32
app/src/main/AndroidManifest.xml
Normal 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>
|
13
app/src/main/java/com/example/myapplication/GlobalUser.kt
Normal file
@ -0,0 +1,13 @@
|
||||
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!!
|
||||
}
|
||||
}
|
||||
}
|
43
app/src/main/java/com/example/myapplication/MainActivity.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package com.example.myapplication
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
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()
|
||||
}
|
||||
}
|
18
app/src/main/java/com/example/myapplication/MyApp.kt
Normal 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.OfflineItemRepository
|
||||
|
||||
class MyApp : Application() {
|
||||
val db by lazy { AppDb.getInstance(this) }
|
||||
|
||||
val itemRepository: OfflineItemRepository by lazy {
|
||||
OfflineItemRepository(db.itemDao())
|
||||
}
|
||||
|
||||
val categoryRepository: OfflineCategoryRepository by lazy {
|
||||
OfflineCategoryRepository(db.categoryDao())
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package ru.ulstu.`is`.pmu.api
|
||||
|
||||
import androidx.room.Insert
|
||||
import com.example.myapplication.api.model.CategoryRemote
|
||||
import com.example.myapplication.api.model.ItemRemote
|
||||
import com.example.myapplication.api.model.UserItemListRemote
|
||||
import com.example.myapplication.api.model.UserItemRemote
|
||||
import com.example.myapplication.api.model.UserRemote
|
||||
import com.example.myapplication.database.entities.Category
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
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("items")
|
||||
suspend fun getItems(
|
||||
@Query("_page") page: Int,
|
||||
@Query("_limit") limit: Int,
|
||||
): List<ItemRemote>
|
||||
|
||||
@GET("categories")
|
||||
suspend fun getCategories(): List<CategoryRemote>
|
||||
|
||||
@POST("items")
|
||||
suspend fun createItem(
|
||||
@Body item: ItemRemote,
|
||||
): ItemRemote
|
||||
|
||||
@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}/userItemCart?_expand=item")
|
||||
suspend fun getUserItemsCartById(
|
||||
@Path("id") id: Int): List<UserItemListRemote>
|
||||
|
||||
@GET("users/{id}/userItemFavorite?_expand=item")
|
||||
suspend fun getUserItemsFavoriteById(
|
||||
@Path("id") id: Int): List<UserItemListRemote>
|
||||
|
||||
@GET("userItemCart")
|
||||
suspend fun getItemCart(
|
||||
@Query("userId") userId: Int,
|
||||
@Query("itemId") itemId: Int
|
||||
): List<UserItemRemote>
|
||||
|
||||
@GET("userItemFavorite")
|
||||
suspend fun getItemFavorite(
|
||||
@Query("userId") userId: Int,
|
||||
@Query("itemId") itemId: Int
|
||||
): List<UserItemRemote>
|
||||
|
||||
@POST("users")
|
||||
suspend fun createUser(@Body user: UserRemote)
|
||||
|
||||
@POST("userItemCart")
|
||||
suspend fun addItemCart(@Body userItem: UserItemRemote)
|
||||
|
||||
@POST("userItemFavorite")
|
||||
suspend fun addItemFavorite(@Body userItem: UserItemRemote)
|
||||
|
||||
@DELETE("userItemCart/{id}")
|
||||
suspend fun deleteCartItem(
|
||||
@Path("id") id: Int,
|
||||
)
|
||||
|
||||
@DELETE("userItemFavorite{id}")
|
||||
suspend fun deleteFavoriteItem(
|
||||
@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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
@ -0,0 +1,41 @@
|
||||
package com.example.myapplication.api.model
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
@Serializable
|
||||
data class ItemRemote(
|
||||
val id: Int = 0,
|
||||
val name: String,
|
||||
val price: Double,
|
||||
val img: ByteArray,
|
||||
val categoryId: Int
|
||||
)
|
||||
|
||||
fun ItemRemote.toItem(): Item {
|
||||
val imgBitmap: Bitmap = BitmapFactory.decodeByteArray(img, 0, img.size)
|
||||
return Item(
|
||||
id,
|
||||
name,
|
||||
price,
|
||||
imgBitmap,
|
||||
categoryId
|
||||
)
|
||||
}
|
||||
|
||||
fun Item.toItemRemote(): ItemRemote {
|
||||
val outputStream = ByteArrayOutputStream();
|
||||
img.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
val imgByteArr: ByteArray = outputStream.toByteArray()
|
||||
|
||||
return ItemRemote(
|
||||
0,
|
||||
name,
|
||||
price,
|
||||
imgByteArr,
|
||||
categoryId
|
||||
)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.example.myapplication.api.model
|
||||
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
data class UserItemListRemote(
|
||||
val id: Int,
|
||||
val userId: Int,
|
||||
val itemId: Int,
|
||||
val item: ItemRemote
|
||||
)
|
||||
|
||||
fun UserItemListRemote.toItemList(): Item {
|
||||
return item.toItem()
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.example.myapplication.api.model
|
||||
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UserItemRemote(
|
||||
val id: Int,
|
||||
val userId: Int,
|
||||
val itemId: Int
|
||||
)
|
||||
|
||||
fun UserItemCart.toUserItemRemote(): UserItemRemote {
|
||||
return UserItemRemote(
|
||||
0,
|
||||
userId,
|
||||
itemId,
|
||||
)
|
||||
}
|
||||
|
||||
fun UserItemFavorite.toUserItemRemote(): UserItemRemote {
|
||||
return UserItemRemote(
|
||||
0,
|
||||
userId,
|
||||
itemId
|
||||
)
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
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.model.toItem
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.database.entities.RemoteKeyType
|
||||
import com.example.myapplication.database.entities.RemoteKeys
|
||||
import com.example.myapplication.database.repository.OfflineItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineRemoteKeyRepository
|
||||
import retrofit2.HttpException
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
import java.io.IOException
|
||||
|
||||
@OptIn(ExperimentalPagingApi::class)
|
||||
class ItemRemoteMediator(
|
||||
private val service: MyServerService,
|
||||
private val dbItemRepository: OfflineItemRepository,
|
||||
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||
private val categoryRestRepository: RestCategoryRepository,
|
||||
private val database: AppDb
|
||||
) : RemoteMediator<Int, Item>() {
|
||||
|
||||
override suspend fun initialize(): InitializeAction {
|
||||
return InitializeAction.LAUNCH_INITIAL_REFRESH
|
||||
}
|
||||
|
||||
override suspend fun load(
|
||||
loadType: LoadType,
|
||||
state: PagingState<Int, Item>
|
||||
): MediatorResult {
|
||||
val page = when (loadType) {
|
||||
LoadType.REFRESH -> {
|
||||
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
|
||||
remoteKeys?.nextKey?.minus(1) ?: 1
|
||||
}
|
||||
|
||||
LoadType.PREPEND -> {
|
||||
val remoteKeys = getRemoteKeyForFirstItem(state)
|
||||
remoteKeys?.prevKey
|
||||
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||
}
|
||||
|
||||
LoadType.APPEND -> {
|
||||
val remoteKeys = getRemoteKeyForLastItem(state)
|
||||
remoteKeys?.nextKey
|
||||
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val items = service.getItems(page, state.config.pageSize)
|
||||
val endOfPaginationReached = items.isEmpty()
|
||||
database.withTransaction {
|
||||
if (loadType == LoadType.REFRESH) {
|
||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ITEM)
|
||||
dbItemRepository.clearItems()
|
||||
}
|
||||
val prevKey = if (page == 1) null else page - 1
|
||||
val nextKey = if (endOfPaginationReached) null else page + 1
|
||||
val keys = items.map {
|
||||
RemoteKeys(
|
||||
entityId = it.id,
|
||||
type = RemoteKeyType.ITEM,
|
||||
prevKey = prevKey,
|
||||
nextKey = nextKey
|
||||
)
|
||||
}
|
||||
categoryRestRepository.getAll()
|
||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||
dbItemRepository.insertAll(items.map { it.toItem() })
|
||||
}
|
||||
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
||||
} catch (exception: IOException) {
|
||||
return MediatorResult.Error(exception)
|
||||
} catch (exception: HttpException) {
|
||||
return MediatorResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Item>): RemoteKeys? {
|
||||
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
|
||||
?.let { item ->
|
||||
dbRemoteKeyRepository.getAllRemoteKeys(item.itemId!!, RemoteKeyType.ITEM)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Item>): RemoteKeys? {
|
||||
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
|
||||
?.let { item ->
|
||||
dbRemoteKeyRepository.getAllRemoteKeys(item.itemId!!, RemoteKeyType.ITEM)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRemoteKeyClosestToCurrentPosition(
|
||||
state: PagingState<Int, Item>
|
||||
): RemoteKeys? {
|
||||
return state.anchorPosition?.let { position ->
|
||||
state.closestItemToPosition(position)?.itemId?.let { itemUid ->
|
||||
dbRemoteKeyRepository.getAllRemoteKeys(itemUid, RemoteKeyType.ITEM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.model.toItemRemote
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.dao.ItemDao
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.database.repository.ItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineRemoteKeyRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
|
||||
class ItemsRestRepository(
|
||||
private val service: MyServerService,
|
||||
private val itemDao: ItemDao,
|
||||
private val dbItemRepository: OfflineItemRepository,
|
||||
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
|
||||
private val categoryRestRepository: RestCategoryRepository,
|
||||
private val database: AppDb
|
||||
): ItemRepository {
|
||||
@OptIn(ExperimentalPagingApi::class)
|
||||
override fun getAll(): Flow<PagingData<Item>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 8,
|
||||
prefetchDistance = 2,
|
||||
enablePlaceholders = true,
|
||||
initialLoadSize = 12,
|
||||
maxSize = 24
|
||||
),
|
||||
remoteMediator = ItemRemoteMediator(
|
||||
service,
|
||||
dbItemRepository,
|
||||
dbRemoteKeyRepository,
|
||||
categoryRestRepository,
|
||||
database,
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
itemDao.getAll()
|
||||
}
|
||||
).flow
|
||||
|
||||
override fun getById(id: Int): Flow<Item> = itemDao.getById(id)
|
||||
|
||||
@OptIn(ExperimentalPagingApi::class)
|
||||
override fun getByCategory(category_id: Int): Flow<PagingData<Item>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 8,
|
||||
prefetchDistance = 2,
|
||||
enablePlaceholders = true,
|
||||
initialLoadSize = 12,
|
||||
maxSize = 24
|
||||
),
|
||||
remoteMediator = ItemRemoteMediator(
|
||||
service,
|
||||
dbItemRepository,
|
||||
dbRemoteKeyRepository,
|
||||
categoryRestRepository,
|
||||
database,
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
itemDao.getByCategory(category_id)
|
||||
}
|
||||
).flow
|
||||
|
||||
override suspend fun insert(item: Item) {
|
||||
service.createItem(item.toItemRemote())
|
||||
}
|
||||
|
||||
override suspend fun insertAll(items: List<Item>) = itemDao.insertAll(items)
|
||||
|
||||
override suspend fun clearItems() = itemDao.deleteAll()
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.example.myapplication.api.repository
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.example.myapplication.api.model.toCategory
|
||||
import com.example.myapplication.database.entities.Category
|
||||
import com.example.myapplication.database.repository.CategoryRepository
|
||||
import com.example.myapplication.database.repository.OfflineCategoryRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.example.myapplication.api.repository
|
||||
|
||||
import com.example.myapplication.api.model.toItemList
|
||||
import com.example.myapplication.api.model.toUser
|
||||
import com.example.myapplication.api.model.toUserItemRemote
|
||||
import com.example.myapplication.api.model.toUserRemote
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import com.example.myapplication.database.entities.UserWithCartItems
|
||||
import com.example.myapplication.database.entities.UserWithFavoriteItems
|
||||
import com.example.myapplication.database.repository.UserRepository
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
|
||||
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 getUserItemsCartById(id: Int): UserWithCartItems {
|
||||
val items = service.getUserItemsCartById(id).map { it.toItemList() }
|
||||
return UserWithCartItems(service.getUserById(id).toUser(), items)
|
||||
}
|
||||
|
||||
override suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems {
|
||||
val items = service.getUserItemsFavoriteById(id).map { it.toItemList() }
|
||||
return UserWithFavoriteItems(service.getUserById(id).toUser(), items)
|
||||
}
|
||||
|
||||
override suspend fun deleteCartItem(userId: Int, itemId: Int) {
|
||||
val item = service.getItemCart(userId, itemId)[0]
|
||||
service.deleteCartItem(item.id)
|
||||
}
|
||||
|
||||
override suspend fun deleteFavoriteItem(userId: Int, itemId: Int) {
|
||||
val item = service.getItemFavorite(userId, itemId)[0]
|
||||
service.deleteFavoriteItem(item.id)
|
||||
}
|
||||
|
||||
override suspend fun insert(user: User) {
|
||||
service.createUser(user.toUserRemote())
|
||||
}
|
||||
|
||||
override suspend fun addItemCart(userItem: UserItemCart) {
|
||||
val item = service.getItemCart(userItem.userId, userItem.itemId)
|
||||
if(!item.isEmpty()) throw Exception("Уже добавлено")
|
||||
|
||||
service.addItemCart(userItem.toUserItemRemote())
|
||||
}
|
||||
|
||||
override suspend fun addItemFavorite(userItem: UserItemFavorite) {
|
||||
val item = service.getItemFavorite(userItem.userId, userItem.itemId)
|
||||
if(!item.isEmpty()) throw Exception("Уже добавлено")
|
||||
|
||||
service.addItemFavorite(userItem.toUserItemRemote())
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
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 android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.ButtonDefaults
|
||||
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.Color
|
||||
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.DropDown
|
||||
import com.example.myapplication.components.common.Input
|
||||
import com.example.myapplication.database.entities.Category
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.viewModels.CategoryViewModel
|
||||
import com.example.myapplication.viewModels.ItemViewModel
|
||||
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 okhttp3.Dispatcher
|
||||
|
||||
@Composable
|
||||
fun AddTovar(
|
||||
navController: NavController,
|
||||
id: Int = 0,
|
||||
itemViewModel: ItemViewModel = viewModel(factory = ItemViewModel.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.smart1
|
||||
)) }
|
||||
|
||||
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 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
|
||||
.padding(start = 30.dp, end = 30.dp, top = 100.dp)
|
||||
.verticalScroll(rememberScrollState())) {
|
||||
Input("Название", nameState)
|
||||
Input("Цена", priceState)
|
||||
DropDown(categories, categoryState)
|
||||
Image(
|
||||
bitmap = img.value.asImageBitmap(),
|
||||
contentDescription = "editplaceholder",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.size(384.dp)
|
||||
.padding(8.dp)
|
||||
.align(Alignment.CenterHorizontally))
|
||||
Button(
|
||||
onClick = {
|
||||
launcher.launch("image/*")
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)) {
|
||||
Text("Выбрать картинку", fontSize = 20.sp)
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
itemViewModel.insert(Item(null, nameState.value, priceState.value.toDouble(), img.value, categoryState.value!!.id!!))
|
||||
navController.navigate("main/0")
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)) {
|
||||
Text("Сохранить", fontSize = 20.sp)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
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.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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.components.common.Input
|
||||
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||
import com.example.myapplication.viewModels.UserViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun Authorization(
|
||||
navController: NavController,
|
||||
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
val loginState = remember { mutableStateOf("") }
|
||||
val passState = remember { mutableStateOf("") }
|
||||
|
||||
Column(modifier = Modifier.padding(start=30.dp, end=30.dp, top=100.dp)) {
|
||||
Input("Логин", loginState)
|
||||
Input("Пароль", passState)
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
val user = userViewModel.getByAuth(loginState.value, passState.value)
|
||||
if(user == null) {
|
||||
val toast = Toast.makeText(context, "Неправильный логин или пароль", Toast.LENGTH_SHORT)
|
||||
toast.show()
|
||||
return@launch
|
||||
}
|
||||
|
||||
userViewModel.setUserId(user!!.userId!!)
|
||||
navController.navigate("catalog")
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)) {
|
||||
Text("Войти", fontSize = 20.sp)
|
||||
}
|
||||
Button(
|
||||
onClick = {navController.navigate("registration")},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Color.Black),
|
||||
colors= ButtonDefaults.buttonColors(
|
||||
containerColor=Color.White,
|
||||
contentColor = Color.Gray
|
||||
)) {
|
||||
Text("Регистрация", fontSize = 20.sp, color=Color.Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
101
app/src/main/java/com/example/myapplication/components/Cart.kt
Normal file
@ -0,0 +1,101 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
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.components.common.CardOption
|
||||
import com.example.myapplication.components.common.CardOptionType
|
||||
import com.example.myapplication.components.common.TovarCard
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.viewModels.ItemViewModel
|
||||
import com.example.myapplication.viewModels.UserViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun Cart(navController: NavController, userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val items = remember { mutableStateListOf<Item>() }
|
||||
val sumPrice = remember { mutableStateOf(0.0) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if(userViewModel.getUserId() == 0) {
|
||||
navController.navigate("authorization")
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val data = userViewModel.getUserItemsCartById(userViewModel.getUserId())
|
||||
items.clear()
|
||||
sumPrice.value = 0.0;
|
||||
data.items.forEach {
|
||||
sumPrice.value += it.price
|
||||
items.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Text("Корзина", fontSize = 35.sp, modifier = Modifier.padding(top=20.dp, bottom=20.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
items.forEach {
|
||||
TovarCard(
|
||||
img = it.img,
|
||||
text = it.name,
|
||||
price = "${it.price}$",
|
||||
componentClick = { navController.navigate("tovar/${it.itemId}") },
|
||||
options = arrayOf(
|
||||
CardOption(CardOptionType.Minus) {
|
||||
coroutineScope.launch {
|
||||
userViewModel.deleteCartItem(userViewModel.getUserId(), it.itemId!!)
|
||||
sumPrice.value -= it.price
|
||||
items.remove(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(start = 30.dp, end = 30.dp),
|
||||
contentAlignment = Alignment.BottomStart,
|
||||
) {
|
||||
Column {
|
||||
Text("Итого ${sumPrice.value}$", fontSize = 30.sp, fontWeight = FontWeight.Bold)
|
||||
Button(onClick = { /*TODO*/ }, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Купить", fontSize = 20.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.example.myapplication.components.common.CatalogItem
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.entities.Category
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.viewModels.CategoryViewModel
|
||||
import com.example.myapplication.viewModels.UserViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun Catalog(
|
||||
navController: NavController,
|
||||
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory),
|
||||
categoryViewModel: CategoryViewModel = viewModel(factory = CategoryViewModel.factory)) {
|
||||
val categories = remember { mutableStateListOf<Category>() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if(userViewModel.getUserId() == 0) {
|
||||
navController.navigate("authorization")
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
categories.addAll(categoryViewModel.getAll())
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Text("Каталог", fontSize = 35.sp, modifier = Modifier.padding(top=20.dp, bottom=20.dp))
|
||||
|
||||
categories.forEach {
|
||||
CatalogItem(it.name) {
|
||||
navController.navigate("main/${it.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.CardOption
|
||||
import com.example.myapplication.components.common.CardOptionType
|
||||
import com.example.myapplication.components.common.TovarCard
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.viewModels.UserViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun Favorites(navController: NavController, userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val items = remember { mutableStateListOf<Item>() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if(userViewModel.getUserId() == 0) {
|
||||
navController.navigate("authorization")
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val data = userViewModel.getUserItemsFavoriteById(userViewModel.getUserId())
|
||||
items.clear()
|
||||
data.items.forEach {
|
||||
items.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Text("Избранное", fontSize = 35.sp, modifier = Modifier.padding(top = 20.dp, bottom = 20.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
items.forEach {
|
||||
TovarCard(
|
||||
img = it.img,
|
||||
text = it.name,
|
||||
price = "${it.price}$",
|
||||
componentClick = { navController.navigate("tovar/${it.itemId}") },
|
||||
options = arrayOf(
|
||||
CardOption(CardOptionType.Minus) {
|
||||
coroutineScope.launch {
|
||||
userViewModel.deleteFavoriteItem(userViewModel.getUserId(), it.itemId!!)
|
||||
items.remove(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
app/src/main/java/com/example/myapplication/components/Main.kt
Normal file
@ -0,0 +1,142 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
import android.widget.Toast
|
||||
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.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material3.Button
|
||||
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.RectangleShape
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.example.myapplication.components.common.CardOption
|
||||
import com.example.myapplication.components.common.CardOptionType
|
||||
import com.example.myapplication.components.common.Input
|
||||
import com.example.myapplication.components.common.TovarCard
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import com.example.myapplication.viewModels.ItemViewModel
|
||||
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
|
||||
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun Main(
|
||||
navController: NavController,
|
||||
catalogId: Int,
|
||||
itemViewModel: ItemViewModel = viewModel(factory = ItemViewModel.factory),
|
||||
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val tovars = when(catalogId) {
|
||||
0 -> itemViewModel.getAll().collectAsLazyPagingItems()
|
||||
else -> itemViewModel.getByCategory(catalogId).collectAsLazyPagingItems()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (userViewModel.getUserId() == 0) {
|
||||
navController.navigate("authorization")
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Button(
|
||||
onClick = {
|
||||
navController.navigate("add-tovar")
|
||||
},
|
||||
shape = RectangleShape,
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.height(40.dp)
|
||||
) {
|
||||
Text("Добавить товар")
|
||||
}
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(2),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
items(
|
||||
count = tovars.itemCount,
|
||||
key = tovars.itemKey { item -> item.itemId!! }
|
||||
) {index ->
|
||||
val it = tovars[index]!!
|
||||
TovarCard(
|
||||
img = it.img,
|
||||
text = it.name,
|
||||
price = "${it.price}$",
|
||||
componentClick = { navController.navigate("tovar/${it.itemId}") },
|
||||
options = arrayOf(
|
||||
CardOption(CardOptionType.AddFavorites) {
|
||||
coroutineScope.launch {
|
||||
kotlin.runCatching {
|
||||
userViewModel.addItemFavorite(UserItemFavorite(userViewModel.getUserId(), it.itemId!!))
|
||||
}
|
||||
.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()
|
||||
}
|
||||
}
|
||||
},
|
||||
CardOption(CardOptionType.AddCart) {
|
||||
coroutineScope.launch {
|
||||
kotlin.runCatching {
|
||||
userViewModel.addItemCart(UserItemCart(userViewModel.getUserId(), it.itemId!!))
|
||||
}
|
||||
.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()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.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.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.example.myapplication.components.common.Input
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.viewModels.UserViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@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.padding(start=30.dp, end=30.dp, top=100.dp)) {
|
||||
Input("Email", mailState)
|
||||
Input("Логин", loginState)
|
||||
Input("Пароль", passState)
|
||||
Input("Повторите Пароль", repeatPassState)
|
||||
Button(
|
||||
onClick = {
|
||||
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")
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)) {
|
||||
Text("Регистрация", fontSize = 20.sp)
|
||||
}
|
||||
Button(
|
||||
onClick = {navController.navigate("authorization")},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Color.Black),
|
||||
colors= ButtonDefaults.buttonColors(
|
||||
containerColor= Color.White,
|
||||
contentColor = Color.Gray
|
||||
)) {
|
||||
Text("Авторизация", fontSize = 20.sp, color= Color.Black)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.example.myapplication.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
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 com.example.myapplication.R
|
||||
import com.example.myapplication.database.AppDb
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.viewModels.ItemViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun Tovar(id: Int, itemViewModel: ItemViewModel = viewModel(factory = ItemViewModel.factory)) {
|
||||
|
||||
val (item, setItem) = remember { mutableStateOf<Item?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
itemViewModel.getById(id).collect{
|
||||
setItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
item?.let{
|
||||
Text(it.name, fontSize = 35.sp, modifier = Modifier.padding(top=20.dp, bottom=20.dp))
|
||||
Image(
|
||||
bitmap= it.img.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(250.dp)
|
||||
)
|
||||
Text("${item.price}$", fontSize = 20.sp)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.myapplication.components.common
|
||||
|
||||
class CardOption(
|
||||
val type : CardOptionType,
|
||||
val onClick: () -> Unit = {}
|
||||
) {
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.example.myapplication.components.common
|
||||
|
||||
enum class CardOptionType {
|
||||
AddFavorites,
|
||||
AddCart,
|
||||
Edit,
|
||||
Minus;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.example.myapplication.components.common
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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 com.example.myapplication.R
|
||||
|
||||
@Composable
|
||||
fun CatalogItem(text: String, onClick: () -> Unit) {
|
||||
Button(
|
||||
onClick = {onClick()},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Color.Black),
|
||||
shape= RectangleShape,
|
||||
colors= ButtonDefaults.buttonColors(
|
||||
containerColor= Color(0xFFCACACA),
|
||||
contentColor = Color.Gray
|
||||
)
|
||||
) {
|
||||
Text(text, fontSize = 25.sp, color= Color.Black)
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.example.myapplication.components.common
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
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.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Input(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(10.dp))
|
||||
.fillMaxWidth()
|
||||
.height(height),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.example.myapplication.components.common
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
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.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
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.RectangleShape
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.myapplication.R
|
||||
|
||||
private val optionToIcon = mapOf<CardOptionType, Int>(
|
||||
CardOptionType.AddCart to R.drawable.add_cart_icon,
|
||||
CardOptionType.AddFavorites to R.drawable.add_favorites_icon,
|
||||
CardOptionType.Edit to R.drawable.edit_icon,
|
||||
CardOptionType.Minus to R.drawable.minus_icon,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun TovarCard(
|
||||
img: Bitmap,
|
||||
text: String,
|
||||
price: String,
|
||||
componentClick : () -> Unit,
|
||||
options: Array<CardOption>) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.size(185.dp, 240.dp)
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Row {
|
||||
Button(
|
||||
onClick = componentClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor= Color.Transparent,
|
||||
contentColor = Color.Gray
|
||||
),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
shape = RectangleShape
|
||||
) {
|
||||
Image(
|
||||
bitmap = img.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(150.dp)
|
||||
)
|
||||
}
|
||||
Column {
|
||||
options.forEach {option ->
|
||||
Button(
|
||||
onClick = option.onClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor= Color.Transparent,
|
||||
contentColor = Color.Gray
|
||||
),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
modifier = Modifier.size(20.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = optionToIcon.getValue(option.type)),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(text)
|
||||
Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.BottomStart) {
|
||||
Text(
|
||||
price,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.example.myapplication.database
|
||||
|
||||
import android.content.Context
|
||||
import com.example.myapplication.database.repository.CategoryRepository
|
||||
import com.example.myapplication.database.repository.ItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineCategoryRepository
|
||||
import com.example.myapplication.database.repository.OfflineItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineUserRepository
|
||||
import com.example.myapplication.database.repository.UserRepository
|
||||
|
||||
interface AppContainer {
|
||||
val userRepository: UserRepository
|
||||
val itemRepository: ItemRepository
|
||||
val categoryRepository: CategoryRepository
|
||||
}
|
||||
|
||||
class AppDataContainer(private val context: Context) : AppContainer {
|
||||
override val userRepository: UserRepository by lazy {
|
||||
OfflineUserRepository(AppDb.getInstance(context).userDao())
|
||||
}
|
||||
override val itemRepository: ItemRepository by lazy {
|
||||
OfflineItemRepository(AppDb.getInstance(context).itemDao())
|
||||
}
|
||||
|
||||
override val categoryRepository: CategoryRepository by lazy {
|
||||
OfflineCategoryRepository(AppDb.getInstance(context).categoryDao())
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TIMEOUT = 5000L
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
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.ItemDao
|
||||
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.Item
|
||||
import com.example.myapplication.database.entities.RemoteKeys
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Database(entities = [Item::class, Category::class, User::class, UserItemCart::class, UserItemFavorite::class, RemoteKeys::class], version = 1, exportSchema = false)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDb : RoomDatabase() {
|
||||
abstract fun itemDao(): ItemDao
|
||||
abstract fun categoryDao(): CategoryDao
|
||||
abstract fun userDao(): UserDao
|
||||
abstract fun remoteKeysDao(): RemoteKeysDao
|
||||
|
||||
companion object {
|
||||
private const val DB_NAME: String = "myApp.db"
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: AppDb? = null
|
||||
|
||||
private suspend fun populateDatabase(context:Context) {
|
||||
INSTANCE?.let { database ->
|
||||
// Categorys
|
||||
val categoryDao = database.categoryDao()
|
||||
|
||||
categoryDao.insert(Category(1, "Смартфоны"))
|
||||
categoryDao.insert(Category(2, "Компьютеры"))
|
||||
categoryDao.insert(Category(3, "Телевизоры"))
|
||||
categoryDao.insert(Category(4, "Для кухни"))
|
||||
categoryDao.insert(Category(5, "Для дома"))
|
||||
// Items
|
||||
val itemDao = database.itemDao()
|
||||
|
||||
val smart1: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart1)
|
||||
val smart2: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart2)
|
||||
val smart3: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart3)
|
||||
val smart4: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart4)
|
||||
|
||||
itemDao.insert(Item(1, "DEXP G450 One 8 GB red", 400.0, smart1,1))
|
||||
itemDao.insert(Item(2, "INOI A22 Lite 8 GB black", 500.0, smart2,1))
|
||||
itemDao.insert(Item(3, "DEXP A455 16 GB blue", 250.0, smart3,1))
|
||||
itemDao.insert(Item(4, "BQ 5031G Fun 8 GB green", 300.0, smart4,1))
|
||||
|
||||
val userDao = database.userDao()
|
||||
userDao.insert(User(1, "Иванов И.И", "ivanov", "1234"))
|
||||
|
||||
database.userDao().addItemCart(UserItemCart(1, 1))
|
||||
database.userDao().addItemCart(UserItemCart(1, 2))
|
||||
|
||||
database.userDao().addItemFavorite(UserItemFavorite(1, 3))
|
||||
database.userDao().addItemFavorite(UserItemFavorite(1, 4))
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CategoryDao {
|
||||
@Query("select * from categories")
|
||||
fun getAll(): List<Category>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(category: Category)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.example.myapplication.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ItemDao {
|
||||
@Query("select * from items")
|
||||
fun getAll(): PagingSource<Int, Item>
|
||||
|
||||
@Query("select * from items where items.itemId = :id")
|
||||
fun getById(id: Int): Flow<Item>
|
||||
|
||||
@Query("select * from items where items.category_id = :category_id")
|
||||
fun getByCategory(category_id: Int): PagingSource<Int, Item>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(item: Item)
|
||||
|
||||
@Insert
|
||||
suspend fun insertAll(items: List<Item>)
|
||||
|
||||
@Query("delete from items")
|
||||
suspend fun deleteAll()
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
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)
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
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.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import com.example.myapplication.database.entities.UserWithCartItems
|
||||
import com.example.myapplication.database.entities.UserWithFavoriteItems
|
||||
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 useritemcart where useritemcart.userId == :userId and useritemcart.itemId == :itemId")
|
||||
suspend fun deleteCartItem(userId: Int, itemId: Int)
|
||||
|
||||
@Query("delete from useritemfavorite where useritemfavorite.userId == :userId and useritemfavorite.itemId == :itemId")
|
||||
suspend fun deleteFavoriteItem(userId: Int, itemId: Int)
|
||||
|
||||
@Insert
|
||||
suspend fun insert(user: User)
|
||||
|
||||
@Query("select * from users where users.userId = :id")
|
||||
fun getUserItemsCartById(id: Int): UserWithCartItems
|
||||
|
||||
@Insert
|
||||
suspend fun addItemCart(userItem: UserItemCart)
|
||||
|
||||
@Query("select * from users where users.userId = :id")
|
||||
fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems
|
||||
|
||||
@Insert
|
||||
suspend fun addItemFavorite(userItem: UserItemFavorite)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.example.myapplication.database.entities
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
|
||||
@Entity(
|
||||
tableName = "items",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Category::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["category_id"],
|
||||
onDelete = ForeignKey.RESTRICT,
|
||||
onUpdate = ForeignKey.RESTRICT
|
||||
)
|
||||
]
|
||||
)
|
||||
data class Item(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var itemId: Int?,
|
||||
@ColumnInfo(name="name")
|
||||
val name: 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 Item
|
||||
if (itemId != other.itemId) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return itemId ?: -1
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.example.myapplication.database.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import com.example.myapplication.database.entities.Item
|
||||
|
||||
enum class RemoteKeyType(private val type: String) {
|
||||
ITEM(Item::class.simpleName ?: "Item");
|
||||
|
||||
@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?
|
||||
)
|
@ -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="mail")
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.example.myapplication.database.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(primaryKeys = ["userId", "itemId"])
|
||||
data class UserItemCart(
|
||||
val userId: Int,
|
||||
val itemId: Int
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.example.myapplication.database.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(primaryKeys = ["userId", "itemId"])
|
||||
data class UserItemFavorite (
|
||||
val userId: Int,
|
||||
val itemId: Int
|
||||
)
|
@ -0,0 +1,15 @@
|
||||
package com.example.myapplication.database.entities
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Junction
|
||||
import androidx.room.Relation
|
||||
|
||||
data class UserWithCartItems (
|
||||
@Embedded val user : User,
|
||||
@Relation(
|
||||
parentColumn = "userId",
|
||||
entityColumn = "itemId",
|
||||
associateBy = Junction(UserItemCart::class)
|
||||
)
|
||||
val items : List<Item>
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.example.myapplication.database.entities
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Junction
|
||||
import androidx.room.Relation
|
||||
|
||||
data class UserWithFavoriteItems (
|
||||
@Embedded
|
||||
val user : User,
|
||||
@Relation(
|
||||
parentColumn = "userId",
|
||||
entityColumn = "itemId",
|
||||
associateBy = Junction(UserItemFavorite::class)
|
||||
)
|
||||
val items : List<Item>
|
||||
)
|
@ -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)
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.example.myapplication.database.repository
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import androidx.room.Query
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ItemRepository {
|
||||
fun getAll(): Flow<PagingData<Item>>
|
||||
fun getById(id: Int): Flow<Item>
|
||||
fun getByCategory(category_id: Int): Flow<PagingData<Item>>
|
||||
suspend fun insert(item: Item)
|
||||
suspend fun insertAll(items: List<Item>)
|
||||
suspend fun clearItems()
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.example.myapplication.database.repository
|
||||
|
||||
import com.example.myapplication.database.dao.CategoryDao
|
||||
import com.example.myapplication.database.entities.Category
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository {
|
||||
override suspend fun getAll(): List<Category> = categoryDao.getAll()
|
||||
override suspend fun insert(category: Category) = categoryDao.insert(category)
|
||||
}
|
@ -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.ItemDao
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class OfflineItemRepository(private val itemDao: ItemDao) : ItemRepository {
|
||||
override fun getAll(): Flow<PagingData<Item>> {
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 8,
|
||||
prefetchDistance = 2,
|
||||
enablePlaceholders = true,
|
||||
initialLoadSize = 12,
|
||||
maxSize = 24
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
itemDao.getAll()
|
||||
}
|
||||
).flow
|
||||
}
|
||||
override fun getById(id: Int): Flow<Item> = itemDao.getById(id)
|
||||
override fun getByCategory(category_id: Int): Flow<PagingData<Item>> {
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 8,
|
||||
prefetchDistance = 2,
|
||||
enablePlaceholders = true,
|
||||
initialLoadSize = 12,
|
||||
maxSize = 24
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
itemDao.getByCategory(category_id)
|
||||
}
|
||||
).flow
|
||||
}
|
||||
override suspend fun insert(item: Item) = itemDao.insert(item)
|
||||
override suspend fun insertAll(items: List<Item>) = itemDao.insertAll(items)
|
||||
|
||||
override suspend fun clearItems() = itemDao.deleteAll()
|
||||
}
|
@ -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)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
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.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import com.example.myapplication.database.entities.UserWithCartItems
|
||||
import com.example.myapplication.database.entities.UserWithFavoriteItems
|
||||
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 getUserItemsCartById(id: Int): UserWithCartItems = userDao.getUserItemsCartById(id)
|
||||
override suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems = userDao.getUserItemsFavoriteById(id)
|
||||
|
||||
override suspend fun deleteCartItem(userId: Int, itemId: Int) = userDao.deleteCartItem(userId, itemId)
|
||||
override suspend fun deleteFavoriteItem(userId: Int, itemId: Int) = userDao.deleteFavoriteItem(userId, itemId)
|
||||
|
||||
override suspend fun insert(user: User) = userDao.insert(user)
|
||||
override suspend fun addItemCart(userItem: UserItemCart) = userDao.addItemCart(userItem)
|
||||
override suspend fun addItemFavorite(userItem: UserItemFavorite) = userDao.addItemFavorite(userItem)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
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)
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.example.myapplication.database.repository
|
||||
|
||||
import androidx.room.Query
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import com.example.myapplication.database.entities.UserWithCartItems
|
||||
import com.example.myapplication.database.entities.UserWithFavoriteItems
|
||||
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 getUserItemsCartById(id: Int): UserWithCartItems
|
||||
suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems
|
||||
|
||||
suspend fun deleteCartItem(userId: Int, itemId: Int)
|
||||
suspend fun deleteFavoriteItem(userId: Int, itemId: Int)
|
||||
|
||||
suspend fun insert(user: User)
|
||||
suspend fun addItemCart(userItem: UserItemCart)
|
||||
suspend fun addItemFavorite(userItem: UserItemFavorite)
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.example.myapplication.navigation
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.Image
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
class NavItem(
|
||||
val route : String,
|
||||
val label : String,
|
||||
@DrawableRes val icon : Int
|
||||
) {
|
||||
}
|
109
app/src/main/java/com/example/myapplication/navigation/Navbar.kt
Normal file
@ -0,0 +1,109 @@
|
||||
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.BottomAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
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.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
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.AddTovar
|
||||
import com.example.myapplication.components.Authorization
|
||||
import com.example.myapplication.components.Cart
|
||||
import com.example.myapplication.components.Catalog
|
||||
import com.example.myapplication.components.Favorites
|
||||
import com.example.myapplication.components.Main
|
||||
import com.example.myapplication.components.Registration
|
||||
import com.example.myapplication.components.Tovar
|
||||
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_icon),
|
||||
NavItem("catalog", "Каталог", R.drawable.catalog_icon),
|
||||
NavItem("cart", "Корзина", R.drawable.cart_icon),
|
||||
NavItem("favorites", "Избранное", R.drawable.favorites_icon)
|
||||
)
|
||||
|
||||
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("catalog") { Catalog(navController) }
|
||||
composable("cart") { Cart(navController) }
|
||||
composable("favorites") { Favorites(navController) }
|
||||
composable("add-tovar") { AddTovar(navController) }
|
||||
composable(
|
||||
"main/{id}",
|
||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||
) {backStackEntry ->
|
||||
backStackEntry.arguments?.let { Main(navController, it.getInt("id")) }
|
||||
}
|
||||
composable(
|
||||
"tovar/{id}",
|
||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||
) { backStackEntry ->
|
||||
backStackEntry.arguments?.let { Tovar(it.getInt("id")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name="Navbar")
|
||||
@Composable
|
||||
fun PreviewNavbar() {
|
||||
MyApplicationTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||
Navbar()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
@ -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
|
||||
)
|
||||
}
|
34
app/src/main/java/com/example/myapplication/ui/theme/Type.kt
Normal 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
|
||||
)
|
||||
*/
|
||||
)
|
@ -0,0 +1,33 @@
|
||||
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.repository.RestCategoryRepository
|
||||
import com.example.myapplication.database.entities.Category
|
||||
import com.example.myapplication.database.repository.CategoryRepository
|
||||
import com.example.myapplication.database.repository.OfflineCategoryRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
@ -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.repository.ItemsRestRepository
|
||||
import com.example.myapplication.api.repository.RestCategoryRepository
|
||||
import com.example.myapplication.database.entities.Item
|
||||
import com.example.myapplication.database.repository.ItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineItemRepository
|
||||
import com.example.myapplication.database.repository.OfflineRemoteKeyRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
|
||||
class ItemViewModel(
|
||||
private val itemRepository: ItemRepository
|
||||
) : 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 ItemViewModel(ItemsRestRepository(
|
||||
MyServerService.getInstance(),
|
||||
app.db.itemDao(),
|
||||
app.itemRepository,
|
||||
OfflineRemoteKeyRepository(app.db.remoteKeysDao()),
|
||||
RestCategoryRepository(MyServerService.getInstance(), app.categoryRepository),
|
||||
app.db
|
||||
)) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAll() : Flow<PagingData<Item>> {
|
||||
return itemRepository.getAll().cachedIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun getById(id: Int) : Flow<Item> {
|
||||
return itemRepository.getById(id)
|
||||
}
|
||||
|
||||
fun getByCategory(cId: Int): Flow<PagingData<Item>> {
|
||||
return itemRepository.getByCategory(cId).cachedIn(viewModelScope)
|
||||
}
|
||||
|
||||
suspend fun insert(item: Item) {
|
||||
itemRepository.insert(item)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.example.myapplication.viewModels
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.repository.UserRestRepository
|
||||
import com.example.myapplication.database.entities.User
|
||||
import com.example.myapplication.database.entities.UserItemCart
|
||||
import com.example.myapplication.database.entities.UserItemFavorite
|
||||
import com.example.myapplication.database.entities.UserWithCartItems
|
||||
import com.example.myapplication.database.entities.UserWithFavoriteItems
|
||||
import com.example.myapplication.database.repository.OfflineUserRepository
|
||||
import com.example.myapplication.database.repository.UserRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||
|
||||
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 deleteCartItem(userId: Int, itemId: Int) {
|
||||
userRepository.deleteCartItem(userId, itemId)
|
||||
}
|
||||
suspend fun deleteFavoriteItem(userId: Int, itemId: Int) {
|
||||
userRepository.deleteFavoriteItem(userId, itemId)
|
||||
}
|
||||
|
||||
|
||||
suspend fun getUserItemsCartById(id: Int): UserWithCartItems {
|
||||
return userRepository.getUserItemsCartById(id)
|
||||
}
|
||||
suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems {
|
||||
return userRepository.getUserItemsFavoriteById(id)
|
||||
}
|
||||
|
||||
suspend fun insert(user: User) {
|
||||
userRepository.insert(user)
|
||||
}
|
||||
suspend fun addItemCart(userItem: UserItemCart) {
|
||||
userRepository.addItemCart(userItem)
|
||||
}
|
||||
suspend fun addItemFavorite(userItem: UserItemFavorite) {
|
||||
userRepository.addItemFavorite(userItem)
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable-hdpi/add_cart_icon.png
Normal file
After Width: | Height: | Size: 356 B |
BIN
app/src/main/res/drawable-hdpi/add_favorites_icon.png
Normal file
After Width: | Height: | Size: 219 B |
BIN
app/src/main/res/drawable-hdpi/cart_icon.png
Normal file
After Width: | Height: | Size: 638 B |
BIN
app/src/main/res/drawable-hdpi/catalog_icon.png
Normal file
After Width: | Height: | Size: 607 B |
BIN
app/src/main/res/drawable-hdpi/edit_icon.png
Normal file
After Width: | Height: | Size: 228 B |
BIN
app/src/main/res/drawable-hdpi/favorites_icon.png
Normal file
After Width: | Height: | Size: 729 B |
BIN
app/src/main/res/drawable-hdpi/home_icon.png
Normal file
After Width: | Height: | Size: 579 B |
BIN
app/src/main/res/drawable-hdpi/minus_icon.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
app/src/main/res/drawable-hdpi/smart1.jpg
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
app/src/main/res/drawable-hdpi/smart2.jpg
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
app/src/main/res/drawable-hdpi/smart3.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
app/src/main/res/drawable-hdpi/smart4.jpg
Normal file
After Width: | Height: | Size: 6.8 KiB |
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
10
app/src/main/res/values/colors.xml
Normal 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>
|