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
|
*.iml
|
||||||
.idea/
|
.gradle
|
||||||
misc.xml
|
/local.properties
|
||||||
deploymentTargetDropDown.xml
|
/.idea/caches
|
||||||
render.experimental.xml
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
# Keystore files
|
/.idea/workspace.xml
|
||||||
*.jks
|
/.idea/navEditor.xml
|
||||||
*.keystore
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
# Google Services (e.g. APIs or Firebase)
|
/build
|
||||||
google-services.json
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
# Android Profiling
|
.cxx
|
||||||
*.hprof
|
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>
|