Compare commits

...

26 Commits

Author SHA1 Message Date
3c184314af reformat 2023-12-28 13:10:27 +04:00
478d995c2f add error 2023-12-28 00:58:27 +04:00
aea5353e4a refactor 2023-12-26 01:02:49 +04:00
57821e0ec3 bug fix 2023-12-26 00:59:24 +04:00
c6f8186bac reformat code 2023-12-26 00:09:10 +04:00
224b950b34 coursework is completed full 2023-12-24 00:56:52 +04:00
4f8d3421dc update card 2023-12-23 02:41:30 +04:00
fa07a913c4 fix 2023-12-22 15:50:42 +03:00
9b89031eba bug fix 2023-12-22 05:21:24 +03:00
41e2ff3ce6 experiment 2023-12-22 02:41:18 +03:00
40c4b4606e clear project 2023-12-22 00:03:32 +03:00
7844cd431e 100% completed 2023-12-21 23:46:27 +03:00
7d5f8c1fc8 80% completed 2023-12-21 22:59:46 +03:00
48e05c3127 lab5 complete 2023-12-11 21:52:51 +03:00
25d2a0318c lab5 2023-12-08 22:04:31 +03:00
c255e104d9 nearly ready 1.0 2023-12-06 19:14:55 +03:00
2b6e2f4ccc nearly ready 2023-12-06 18:50:29 +03:00
6565ea911b not completed lab5 2023-12-05 22:58:26 +03:00
e2d0f1f73a small fix 2023-12-05 18:08:57 +03:00
d03a88c06b small fix 2023-11-27 18:25:55 +03:00
a5f4a61ea9 lab4 2023-11-27 17:59:01 +03:00
938e85d993 small fix 2023-11-18 12:43:37 +03:00
6fb5b51fb8 lab3 2023-11-14 09:13:26 +03:00
5934717cb6 lab2 2023-10-31 08:57:16 +03:00
5007344461 Lab1 2023-10-03 09:47:47 +04:00
1765b03400 Lab1 2023-10-03 09:38:17 +04:00
145 changed files with 7122 additions and 34 deletions

48
.gitignore vendored
View File

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

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
FlowerShopApp

View File

@ -0,0 +1,124 @@
<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" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="0" />
</codeStyleSettings>
</code_scheme>
</component>

View 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
View File

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

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="MainActivity">
<State />
</entry>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

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

6
.idea/kotlinc.xml Normal file
View File

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

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml Normal file
View File

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

6
.idea/vcs.xml Normal file
View File

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

BIN
Report/Отчёт_1.docx Normal file

Binary file not shown.

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

111
app/.idea/workspace.xml Normal file
View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="540b4edc-b8de-442a-b540-e75c26fb8db3" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/../.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/../.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../.idea/inspectionProfiles/Project_Default.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../.idea/kotlinc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/kotlinc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/gradle.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/proguard-rules.pro" beforeDir="false" afterPath="$PROJECT_DIR$/proguard-rules.pro" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/androidTest/java/com/example/flowershop/ExampleInstrumentedTest.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/androidTest/java/com/example/flowershopapp/ExampleInstrumentedTest.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/androidTest/java/com/example/flowershopapp/ExampleInstrumentedTest.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/MainActivity.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/ui/dashboard/DashboardFragment.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/ui/dashboard/DashboardViewModel.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/ui/home/HomeFragment.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/ui/home/HomeViewModel.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/ui/notifications/NotificationsFragment.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershop/ui/notifications/NotificationsViewModel.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/Bouquet/ComposeUI/BouquetCatalog.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/Bouquet/Model/Bouquet.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Favorite.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Favorite.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Login.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Login.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Navigation/MainNavbar.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Navigation/MainNavbar.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Navigation/Screen.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Navigation/Screen.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Profile.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/Profile.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/ShoppingСart.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ComposeUI/ShoppingСart.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/MainActivity.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/MainActivity.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ui/theme/Color.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ui/theme/Color.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ui/theme/Theme.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ui/theme/Theme.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ui/theme/Type.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/flowershopapp/ui/theme/Type.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/drawable/ic_dashboard_black_24dp.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/drawable/ic_home_black_24dp.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/drawable/ic_launcher_background.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/drawable/ic_launcher_background.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/drawable/ic_launcher_foreground.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/drawable/ic_launcher_foreground.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/drawable/ic_notifications_black_24dp.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/drawable/image1.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/drawable/image1.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/layout/activity_main.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/layout/fragment_dashboard.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/layout/fragment_home.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/layout/fragment_notifications.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/menu/bottom_nav_menu.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/navigation/mobile_navigation.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/values-night/themes.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/values/colors.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/values/colors.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/values/dimens.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/values/strings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/values/strings.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/values/themes.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/values/themes.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/xml/backup_rules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/xml/backup_rules.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/res/xml/data_extraction_rules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/res/xml/data_extraction_rules.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/flowershop/ExampleUnitTest.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/flowershopapp/ExampleUnitTest.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/java/com/example/flowershopapp/ExampleUnitTest.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/../build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../gradle.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../gradle.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../gradlew" beforeDir="false" afterPath="$PROJECT_DIR$/../gradlew" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../settings.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/../settings.gradle.kts" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ExternalProjectsManager">
<system id="GRADLE">
<state>
<projects_view>
<tree_state>
<expand />
<select />
</tree_state>
</projects_view>
</state>
</system>
</component>
<component name="ProjectId" id="2Y9eG2i3BJIEW0JLnozi2HpYV82" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"android.gradle.sync.needed": "true",
"cidr.known.project.marker": "true",
"last_opened_file_path": "C:/Users/movavi/AndroidStudioProjects/FlowerShopAppGit"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="540b4edc-b8de-442a-b540-e75c26fb8db3" name="Changes" comment="" />
<created>1699942123160</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1699942123160</updated>
</task>
<servers />
</component>
</project>

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

@ -0,0 +1,91 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "com.example.flowershopapp"
compileSdk = 34
defaultConfig {
applicationId = "com.example.flowershopapp"
minSdk = 26
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}"
}
}
}
kotlin {
jvmToolchain(17)
}
dependencies {
implementation("org.mindrot:jbcrypt:0.4")
implementation("io.coil-kt:coil-compose:2.5.0")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.8.2") // Обновлено до 1.9.0
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.navigation:navigation-compose:2.7.6")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material:material:1.5.4") // Замена material3
implementation("androidx.compose.material3:material3:1.1.2")
implementation("com.google.android.engage:engage-core:1.3.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.compose.ui:ui-graphics-android:1.5.4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
// 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.12.0")
implementation("androidx.paging:paging-compose:3.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
}

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

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

View File

@ -0,0 +1,24 @@
//package com.example.flowershopapp
//
//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.flowershopapp", appContext.packageName)
// }
//}

View File

@ -0,0 +1,31 @@
<?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=".BouquetApplication"
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.FlowerShopApp"
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.FlowerShopApp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,3 @@
package com.example.flowershopapp.API
enum class APIStatus { LOADING, ERROR, DONE }

View File

@ -0,0 +1,125 @@
package com.example.flowershopapp.API.Mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.flowershopapp.API.Model.toBouquet
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.API.Repository.RestOrderBouquetRepository
import com.example.flowershopapp.API.Repository.RestOrderRepository
import com.example.flowershopapp.Database.AppDatabase
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys
import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Repository.Bouquet.OfflineBouquetRepository
import com.example.flowershopapp.Entities.Repository.OrderBouquets.OfflineOrdersWithBouquetsRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class BouquetRemoteMediator(
private val service: MyServerService,
private val dbBouquetRepository: OfflineBouquetRepository,
private val dbOrderBouquetRepository: OfflineOrdersWithBouquetsRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val orderBouquetRestRepository: RestOrderBouquetRepository,
private val orderRestRepository: RestOrderRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Bouquet>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Bouquet>
): 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 bouquets = service.getBouquets(page, state.config.pageSize).map { it.toBouquet() }
val endOfPaginationReached = bouquets.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.BOUQUET)
dbBouquetRepository.deleteAll()
dbOrderBouquetRepository.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = bouquets.map {
it.bouquetId?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.BOUQUET,
prevKey = prevKey,
nextKey = nextKey
)
}
}
orderBouquetRestRepository.getAll()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbBouquetRepository.insertBouquets(bouquets)
}
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, Bouquet>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { bouquet ->
bouquet.bouquetId?.let {
dbRemoteKeyRepository.getAllRemoteKeys(
it,
RemoteKeyType.BOUQUET
)
}
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Bouquet>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { bouquet ->
bouquet.bouquetId?.let {
dbRemoteKeyRepository.getAllRemoteKeys(
it,
RemoteKeyType.BOUQUET
)
}
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Bouquet>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.bouquetId?.let { bouquetUid ->
dbRemoteKeyRepository.getAllRemoteKeys(bouquetUid, RemoteKeyType.BOUQUET)
}
}
}
}

View File

@ -0,0 +1,120 @@
package com.example.flowershopapp.API.Mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.flowershopapp.API.Model.toOrder
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.Database.AppDatabase
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys
import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository
import com.example.flowershopapp.ComposeUI.User.AuthModel
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Repository.Order.OfflineOrderRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class OrderRemoteMediator(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Order>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Order>
): 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 userId = AuthModel.currentUser.userId ?: throw Exception("Error get orders by user")
val orders = service.getOrders(userId, page, state.config.pageSize).map { it.toOrder() }
val endOfPaginationReached = orders.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
dbOrderRepository.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = orders.map {
it.orderId?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.ORDER,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbOrderRepository.insertOrders(orders)
}
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, Order>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { Order ->
Order.orderId?.let {
dbRemoteKeyRepository.getAllRemoteKeys(
it,
RemoteKeyType.ORDER
)
}
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Order>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { Order ->
Order.orderId?.let {
dbRemoteKeyRepository.getAllRemoteKeys(
it,
RemoteKeyType.ORDER
)
}
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Order>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.orderId?.let { OrderUid ->
dbRemoteKeyRepository.getAllRemoteKeys(OrderUid, RemoteKeyType.ORDER)
}
}
}
}

View File

@ -0,0 +1,30 @@
package com.example.flowershopapp.API.Model
import com.example.flowershopapp.Entities.Model.Bouquet
import kotlinx.serialization.Serializable
import java.util.Base64
@Serializable
data class BouquetRemote(
val id: Int? = null,
val name: String = "",
val quantityOfFlowers: Int = 0,
val price: Int = 0,
val image: String = "",
)
fun BouquetRemote.toBouquet(): Bouquet = Bouquet(
id,
name,
quantityOfFlowers,
price,
Base64.getDecoder().decode(image)
)
fun Bouquet.toBouquetRemote(): BouquetRemote = BouquetRemote(
null,
name,
quantityOfFlowers,
price,
image.toString()
)

View File

@ -0,0 +1,26 @@
package com.example.flowershopapp.API.Model
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
import kotlinx.serialization.Serializable
@Serializable
data class OrderBouquetCrossRefRemote(
val id: Int? = null,
val orderId: Int = 0,
val bouquetId: Int = 0,
val count: Int = 0,
)
fun OrderBouquetCrossRefRemote.OrderBouquetCrossRef(): OrderBouquetCrossRef = OrderBouquetCrossRef(
orderId,
bouquetId,
count
)
fun OrderBouquetCrossRef.OrderBouquetCrossRefRemote(): OrderBouquetCrossRefRemote =
OrderBouquetCrossRefRemote(
null,
orderId,
bouquetId,
count
)

View File

@ -0,0 +1,23 @@
package com.example.flowershopapp.API.Model
import com.example.flowershopapp.Entities.Model.OrderByDate
import kotlinx.serialization.Serializable
@Serializable
data class OrderByDateRemote(
val orders: List<OrderRemote> = listOf(),
val orderCount: Int,
val totalSum: Int
)
fun OrderByDateRemote.toOrderByDate(): OrderByDate = OrderByDate(
orders.map { it.toOrder() },
orderCount,
totalSum
)
fun OrderByDate.toOrderByDateRemote(): OrderByDateRemote = OrderByDateRemote(
orders.map { it.toOrderRemote() },
orderCount,
totalSum
)

View File

@ -0,0 +1,26 @@
package com.example.flowershopapp.API.Model
import com.example.flowershopapp.Entities.Model.Order
import kotlinx.serialization.Serializable
@Serializable
data class OrderRemote(
val id: Int? = null,
val date: String = "",
val sum: Int = 0,
val userId: Int = 0,
)
fun OrderRemote.toOrder(): Order = Order(
id,
date,
sum,
userId
)
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
null,
date,
sum,
userId
)

View File

@ -0,0 +1,29 @@
package com.example.flowershopapp.API.Model
import com.example.flowershopapp.Entities.Model.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int? = null,
val userName: String = "",
val dateOfBirth: String = "",
val phoneNumber: String = "",
val password: String = ""
)
fun UserRemote.toUser(): User = User(
id,
userName,
dateOfBirth,
phoneNumber,
password
)
fun User.toUserRemote(): UserRemote = UserRemote(
null,
userName,
dateOfBirth,
phoneNumber,
password
)

View File

@ -0,0 +1,145 @@
package com.example.flowershopapp.API
import com.example.flowershopapp.API.Model.BouquetRemote
import com.example.flowershopapp.API.Model.OrderBouquetCrossRefRemote
import com.example.flowershopapp.API.Model.OrderByDateRemote
import com.example.flowershopapp.API.Model.OrderRemote
import com.example.flowershopapp.API.Model.UserRemote
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
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 {
@POST("users")
suspend fun createUser(
@Body user: UserRemote,
): UserRemote
@GET("users")
suspend fun getUsers(): List<UserRemote>
@GET("users")
suspend fun getUser(
@Query("userName") userName: String
): List<UserRemote>
@PUT("users/{id}")
suspend fun updateUser(
@Path("id") id: Int,
@Body user: UserRemote,
): UserRemote
@DELETE("users/{id}")
suspend fun deleteUser(
@Path("id") id: Int,
): UserRemote
@GET("/ordersByUser")
suspend fun getOrders(
@Query("_userId") userId: Int,
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<OrderRemote>
@GET("orders/{id}")
suspend fun getOrder(
@Path("id") id: Int,
): OrderRemote
@POST("orders")
suspend fun createOrder(
@Body order: OrderRemote,
): OrderRemote
@DELETE("orders/{id}")
suspend fun deleteOrder(
@Path("id") id: Int,
): OrderRemote
@GET("bouquets")
suspend fun getBouquets(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<BouquetRemote>
@GET("/ordersByDate")
suspend fun getOrdersByDate(
@Query("userId") userId: Int,
@Query("start") startDate: String,
@Query("end") endDate: String
): OrderByDateRemote
@GET("/popularBouquets")
suspend fun getPopularBouquets(): List<BouquetRemote>
@GET("bouquets")
suspend fun getAllBouquets(): List<BouquetRemote>
@GET("bouquets/{id}")
suspend fun getBouquet(
@Path("id") id: Int,
): BouquetRemote
@POST("bouquets")
suspend fun createBouquet(
@Body bouquet: BouquetRemote,
): BouquetRemote
@PUT("bouquets/{id}")
suspend fun updateBouquet(
@Path("id") id: Int,
@Body bouquet: BouquetRemote,
): BouquetRemote
@DELETE("bouquets/{id}")
suspend fun deleteBouquet(
@Path("id") id: Int,
): BouquetRemote
@POST("orderBouquetCrossRefs")
suspend fun createOrderBouquet(
@Body orderBouquet: OrderBouquetCrossRefRemote,
)
@GET("orderBouquetCrossRefs")
suspend fun getOrdersBouquets(): List<OrderBouquetCrossRefRemote>
@DELETE("orderBouquetCrossRefs/{id}")
suspend fun deleteOrderBouquet(
@Path("id") id: Int,
)
companion object {
private const val BASE_URL = "http://10.0.2.2:8079/"
@Volatile
private var INSTANCE: MyServerService? = null
fun getInstance(): MyServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(MyServerService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,71 @@
package com.example.flowershopapp.API.Repository
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.flowershopapp.API.Mediator.BouquetRemoteMediator
import com.example.flowershopapp.API.Model.toBouquet
import com.example.flowershopapp.API.Model.toBouquetRemote
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.Database.AppContainer
import com.example.flowershopapp.Database.AppDatabase
import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Repository.Bouquet.BouquetRepository
import com.example.flowershopapp.Entities.Repository.Bouquet.OfflineBouquetRepository
import com.example.flowershopapp.Entities.Repository.OrderBouquets.OfflineOrdersWithBouquetsRepository
import kotlinx.coroutines.flow.Flow
class RestBouquetRepository(
private val service: MyServerService,
private val dbBouquetRepository: OfflineBouquetRepository,
private val dbOrderBouquetRepository: OfflineOrdersWithBouquetsRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val orderBouquetRestRepository: RestOrderBouquetRepository,
private val orderRestRepository: RestOrderRepository,
private val database: AppDatabase
) : BouquetRepository {
override fun getAll(): Flow<PagingData<Bouquet>> {
Log.d(RestBouquetRepository::class.simpleName, "Get Bouquets")
val pagingSourceFactory = { dbBouquetRepository.getAllBouquetsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = BouquetRemoteMediator(
service,
dbBouquetRepository,
dbOrderBouquetRepository,
dbRemoteKeyRepository,
orderBouquetRestRepository,
orderRestRepository,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getPopulateBouquets(): List<Bouquet> {
return service.getPopularBouquets().map { bouquet -> bouquet.toBouquet() }
}
override suspend fun getBouquet(id: Int): Bouquet =
service.getBouquet(id).toBouquet()
override suspend fun insert(bouquet: Bouquet) {
service.createBouquet(bouquet.toBouquetRemote()).toBouquet()
}
override suspend fun update(bouquet: Bouquet) {
bouquet.bouquetId?.let { service.updateBouquet(it, bouquet.toBouquetRemote()).toBouquet() }
}
override suspend fun delete(bouquet: Bouquet) {
bouquet.bouquetId?.let { service.deleteBouquet(it).toBouquet() }
}
}

View File

@ -0,0 +1,39 @@
package com.example.flowershopapp.API.Repository
import android.util.Log
import com.example.flowershopapp.API.Model.OrderBouquetCrossRef
import com.example.flowershopapp.API.Model.OrderBouquetCrossRefRemote
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
import com.example.flowershopapp.Entities.Repository.OrderBouquets.OfflineOrdersWithBouquetsRepository
import com.example.flowershopapp.Entities.Repository.OrderBouquets.OrdersWithBouquetsRepository
class RestOrderBouquetRepository(
private val service: MyServerService,
private val dbOrderBouquetRepository: OfflineOrdersWithBouquetsRepository,
) : OrdersWithBouquetsRepository {
override suspend fun getAll(): List<OrderBouquetCrossRef> {
Log.d(RestOrderBouquetRepository::class.simpleName, "Get OrderBouquets")
val existOrderBouquets = dbOrderBouquetRepository.getAll().toMutableList()
val serverOrderBouquets = service.getOrdersBouquets().map { it.OrderBouquetCrossRef() }
val toDelete = existOrderBouquets.filterNot { serverOrderBouquets.contains(it) }
toDelete.forEach { dbOrderBouquetRepository.delete(it) }
val toAdd = serverOrderBouquets.filterNot { existOrderBouquets.contains(it) }
toAdd.forEach { dbOrderBouquetRepository.insert(it) }
return dbOrderBouquetRepository.getAll()
}
override suspend fun insert(orderBouquet: OrderBouquetCrossRef) {
service.createOrderBouquet(orderBouquet.OrderBouquetCrossRefRemote())
}
override suspend fun delete(orderBouquet: OrderBouquetCrossRef) {
service.deleteOrderBouquet(orderBouquet.orderId)
}
override suspend fun deleteAll() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,83 @@
package com.example.flowershopapp.API.Repository
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.flowershopapp.API.Mediator.OrderRemoteMediator
import com.example.flowershopapp.API.Model.toBouquet
import com.example.flowershopapp.API.Model.toOrder
import com.example.flowershopapp.API.Model.toOrderByDate
import com.example.flowershopapp.API.Model.toOrderRemote
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.Database.AppContainer
import com.example.flowershopapp.Database.AppDatabase
import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.OrderByDate
import com.example.flowershopapp.Entities.Repository.Order.OfflineOrderRepository
import com.example.flowershopapp.Entities.Repository.Order.OrderRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class RestOrderRepository(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : OrderRepository {
override suspend fun getBouquetsByOrder(orderId: Int): Flow<List<Bouquet>> {
var bouquets = service.getAllBouquets()
var bouquetsIdByOrderId =
service.getOrdersBouquets().filter { it.orderId == orderId }.map { it.bouquetId }
return flowOf(bouquets.map { bouquet ->
bouquet.toBouquet()
}.filter { it.bouquetId in bouquetsIdByOrderId })
}
override suspend fun getOrdersByDate(
userId: Int,
startDate: String,
endDate: String
): Flow<OrderByDate> {
return flowOf(service.getOrdersByDate(userId, startDate, endDate).toOrderByDate())
}
override fun getOrdersByUser(id: Int): Flow<PagingData<Order>> {
Log.d(RestOrderRepository::class.simpleName, "Get Orders")
val pagingSourceFactory = { dbOrderRepository.getOrdersByUserPagingSource(id) }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = OrderRemoteMediator(
service,
dbOrderRepository,
dbRemoteKeyRepository,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun insert(order: Order) {
service.createOrder(order.toOrderRemote()).toOrder()
}
override suspend fun insertWithReturn(order: Order): Order {
return service.createOrder(order.toOrderRemote()).toOrder()
}
override suspend fun delete(order: Order) {
order.orderId?.let { service.deleteOrder(it).toOrder() }
}
override suspend fun getById(id: Int): Order {
return service.getOrder(id).toOrder()
}
}

View File

@ -0,0 +1,60 @@
package com.example.flowershopapp.API.Repository
import android.util.Log
import com.example.flowershopapp.API.Model.toUser
import com.example.flowershopapp.API.Model.toUserRemote
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.Entities.Model.User
import com.example.flowershopapp.Entities.Repository.User.OfflineUserRepository
import com.example.flowershopapp.Entities.Repository.User.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class RestUserRepository(
private val service: MyServerService,
private val dbUserRepository: OfflineUserRepository,
) : UserRepository {
override suspend fun getAll(): List<User> {
Log.d(RestUserRepository::class.simpleName, "Get Users")
var existUsersList = listOf<User>()
withContext(Dispatchers.IO) {
existUsersList = dbUserRepository.getAll()
}
var existUsers = existUsersList.associateBy { it.userId }.toMutableMap()
var remoteUsers = service.getUsers()
remoteUsers
.map { it.toUser() }
.forEach { user ->
val existUser = existUsers[user.userId!!]
if (existUser == null) {
dbUserRepository.insert(user)
} else if (existUser != user) {
dbUserRepository.update(user)
}
existUsers[user.userId] = user
}
return service.getUsers().map { it.toUser() }
}
override suspend fun getUserByName(userName: String): User {
return service.getUser(userName)[0].toUser()
}
override suspend fun insert(user: User) {
service.createUser(user.toUserRemote()).toUser()
}
override suspend fun update(user: User) {
user.userId?.let { service.updateUser(it, user.toUserRemote()).toUser() }
}
override suspend fun delete(user: User) {
user.userId?.let { service.deleteUser(it).toUser() }
}
override suspend fun deleteAll() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,14 @@
package com.example.flowershopapp
import android.app.Application
import com.example.flowershopapp.Database.AppContainer
import com.example.flowershopapp.Database.AppDataContainer
class BouquetApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
}

View File

@ -0,0 +1,32 @@
package com.example.flowershopapp.ComposeUI
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.flowershopapp.BouquetApplication
import com.example.flowershopapp.ComposeUI.Bouquet.BouquetCatalogViewModel
import com.example.flowershopapp.ComposeUI.User.OrderViewModel
import com.example.flowershopapp.ComposeUI.User.UserViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
BouquetCatalogViewModel(bouquetApplication().container.bouquetRestRepository)
}
initializer {
OrderViewModel(
this.createSavedStateHandle(),
bouquetApplication().container.orderRestRepository,
bouquetApplication().container.orderBouquetRestRepository
)
}
initializer {
UserViewModel(bouquetApplication().container.userRestRepository)
}
}
}
fun CreationExtras.bouquetApplication(): BouquetApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as BouquetApplication)

View File

@ -0,0 +1,29 @@
package com.example.flowershopapp.ComposeUI
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController
import com.example.flowershopapp.R
import kotlinx.coroutines.delay
@Composable
fun Boot(navController: NavController) {
LaunchedEffect(Unit) {
delay(2000)
navController.navigate("Login")
}
Box(modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(id = R.drawable.image_boot),
contentDescription = "",
contentScale = ContentScale.FillBounds,
modifier = Modifier.fillMaxSize()
)
}
}

View File

@ -0,0 +1,168 @@
package com.example.flowershopapp.ComposeUI.Bouquet
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
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 androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.Entities.Model.Bouquet
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun BouquetCatalog(
navController: NavController,
viewModel: BouquetCatalogViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val bouquetListUiState = viewModel.bouquetListUiState.collectAsLazyPagingItems()
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
bouquetListUiState.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.pullRefresh(state)
) {
Text(
text = "Каталог букетов",
fontFamily = FontFamily.Serif,
fontSize = 40.sp,
fontWeight = FontWeight.W600
)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize()
) {
items(
count = bouquetListUiState.itemCount,
key = bouquetListUiState.itemKey(),
contentType = bouquetListUiState.itemContentType()
) { index ->
val bouquet = bouquetListUiState[index]
if (bouquet != null) {
BouquetCard(bouquet = bouquet)
}
}
}
}
}
@Composable
fun BouquetCard(bouquet: Bouquet) {
var heart by remember { mutableStateOf(FavoriteModel.instance.containsBouquet(bouquet)) }
var cart by remember { mutableStateOf(CartModel.instance.containsBouquet(bouquet)) }
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.wrapContentHeight(),
elevation = CardDefaults.cardElevation(
defaultElevation = 10.dp
),
colors = CardDefaults.cardColors(
containerColor = Color.White
)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box {
bouquet.image?.let { imageData ->
val decodedBitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
val imageBitmap = decodedBitmap.asImageBitmap()
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier
.height(190.dp)
.clip(shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)),
contentScale = ContentScale.FillBounds
)
}
Box(
Modifier
.padding(start = 3.dp, top = 3.dp)
.align(Alignment.TopStart)
) {
Icon(
painter = painterResource(id = cart),
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(shape = RoundedCornerShape(5.dp))
.clickable {
cart = CartModel.instance.updateList(bouquet)
}
)
}
Box(
Modifier
.padding(end = 3.dp, top = 3.dp)
.align(Alignment.TopEnd)
) {
Icon(
painter = painterResource(id = heart),
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(shape = RoundedCornerShape(5.dp))
.clickable {
heart = FavoriteModel.instance.addBouquet(bouquet)
}
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(text = bouquet.name, fontFamily = FontFamily.Serif, fontSize = 20.sp)
Text(
text = "${bouquet.quantityOfFlowers} цветов",
fontFamily = FontFamily.Serif,
fontSize = 15.sp
)
Text(text = "${bouquet.price}", fontFamily = FontFamily.Serif, fontSize = 15.sp)
}
}
}

View File

@ -0,0 +1,40 @@
package com.example.flowershopapp.ComposeUI.Bouquet
import androidx.paging.PagingData
import com.example.flowershopapp.ComposeUI.Network.NetworkViewModel
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Repository.Bouquet.BouquetRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class BouquetCatalogViewModel(
private val bouquetRepository: BouquetRepository
) : NetworkViewModel() {
var bouquetListUiState: Flow<PagingData<Bouquet>> =
MutableStateFlow<PagingData<Bouquet>>(PagingData.empty())
var bouquetPopulateListUiState: List<Bouquet> = emptyList()
init {
collectAllBouquets()
collectPopulateBouquets()
}
private fun collectAllBouquets() {
runInScope(
actionSuccess = {
bouquetListUiState = bouquetRepository.getAll()
}
)
}
fun collectPopulateBouquets() {
runInScope(
actionSuccess = {
bouquetPopulateListUiState = bouquetRepository.getPopulateBouquets()
},
actionError = {
bouquetPopulateListUiState = emptyList()
}
)
}
}

View File

@ -0,0 +1,88 @@
package com.example.flowershopapp.ComposeUI.Bouquet
import androidx.lifecycle.ViewModel
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.R
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class CartModel : ViewModel() {
private val _cartList = MutableStateFlow<List<Pair<Bouquet, Int>>>(emptyList())
val bouquets: StateFlow<List<Pair<Bouquet, Int>>> = _cartList.asStateFlow()
private val _totalSum = MutableStateFlow<Int>(0)
val totalSum: StateFlow<Int> = _totalSum.asStateFlow()
fun updateList(bouquet: Bouquet): Int {
val currentList = _cartList.value.toMutableList()
val isContains =
currentList.find { bouquetPair -> bouquetPair.first.bouquetId == bouquet.bouquetId } != null
return if (isContains) {
val currElement = currentList.find { it.first.bouquetId == bouquet.bouquetId }
currentList.remove(currElement)
_cartList.value = currentList
calculatingTotalSum(currentList.toList())
R.drawable.shopping_cart
} else {
currentList.add(Pair(bouquet, 1))
_cartList.value = currentList
calculatingTotalSum(currentList.toList())
R.drawable.shopping_cart_fill
}
}
fun updateCount(bouquet: Bouquet, isPlus: Boolean) {
val currentList = _cartList.value.toMutableList()
val currElement = currentList.find { it.first.bouquetId == bouquet.bouquetId }
if (currElement != null) {
val indexCurrElement = currentList.indexOf(currElement)
val newCount = if (isPlus) currElement.second + 1 else currElement.second - 1
if (newCount == 0) {
currentList.removeAt(indexCurrElement)
_cartList.value = currentList
calculatingTotalSum(currentList.toList())
return
}
val replaceElement = Pair(currElement.first, newCount)
currentList.removeAt(indexCurrElement)
currentList.add(indexCurrElement, replaceElement)
_cartList.value = currentList
calculatingTotalSum(currentList.toList())
}
}
fun containsBouquet(bouquet: Bouquet): Int {
val currentList = _cartList.value.toMutableList()
return if (currentList.find { it.first.bouquetId == bouquet.bouquetId } != null) {
R.drawable.shopping_cart_fill
} else {
R.drawable.shopping_cart
}
}
fun calculatingTotalSum(list: List<Pair<Bouquet, Int>>) {
_totalSum.value = list.sumOf { it.first.price * it.second }
}
fun getTotalSum(): Int {
return _cartList.value.sumOf { it.first.price * it.second }
}
fun getCount(): Int {
val currentList = _cartList.value.toMutableList()
return currentList.size
}
fun clearBouquets() {
_cartList.value = emptyList()
calculatingTotalSum(emptyList())
}
companion object {
val instance: CartModel by lazy { CartModel() }
}
}

View File

@ -0,0 +1,42 @@
package com.example.flowershopapp.ComposeUI.Bouquet
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
@Composable
fun Favorite(navController: NavController) {
val bouquets by FavoriteModel.instance.bouquets.collectAsState()
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "Избранное",
fontFamily = FontFamily.Serif,
fontSize = 40.sp,
fontWeight = FontWeight.W600
)
val padding = if (bouquets.size == 1) 95.dp else 0.dp
LazyVerticalGrid(
columns = GridCells.Fixed(if (bouquets.size == 1) 1 else 2),
contentPadding = PaddingValues(start = padding, end = padding),
modifier = Modifier.fillMaxSize(),
) {
items(bouquets) { bouquet ->
BouquetCard(bouquet = bouquet)
}
}
}
}

View File

@ -0,0 +1,52 @@
package com.example.flowershopapp.ComposeUI.Bouquet
import androidx.lifecycle.ViewModel
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.R
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FavoriteModel : ViewModel() {
private val _favoriteList = MutableStateFlow<List<Bouquet>>(emptyList())
val bouquets: StateFlow<List<Bouquet>> = _favoriteList.asStateFlow()
fun addBouquet(bouquet: Bouquet): Int {
val currentList = _favoriteList.value.toMutableList()
return if (currentList.contains(bouquet)) {
currentList.remove(bouquet)
_favoriteList.value = currentList
R.drawable.heart_black
} else {
currentList.add(bouquet)
_favoriteList.value = currentList
R.drawable.heart_red
}
}
fun addBouquets(vararg bouquets: Bouquet) {
val currentList = _favoriteList.value.toMutableList()
currentList.addAll(bouquets)
_favoriteList.value = currentList
}
fun removeBouquets(bouquetRemove: Bouquet) {
val currentList = _favoriteList.value.toMutableList()
currentList.removeAll { it.bouquetId == bouquetRemove.bouquetId }
_favoriteList.value = currentList
}
fun containsBouquet(bouquet: Bouquet): Int {
val currentList = _favoriteList.value
return if (currentList.contains(bouquet)) R.drawable.heart_red
else R.drawable.heart_black
}
fun clearBouquets() {
_favoriteList.value = emptyList()
}
companion object {
val instance: FavoriteModel by lazy { FavoriteModel() }
}
}

View File

@ -0,0 +1,72 @@
package com.example.flowershopapp.ComposeUI.Bouquet
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.font.FontFamily
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.flowershopapp.API.APIStatus
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.Network.ErrorPlaceholderWithoutButton
import com.example.flowershopapp.ComposeUI.Network.LoadingPlaceholder
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PopulateBouquets(
navController: NavController,
viewModel: BouquetCatalogViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
LaunchedEffect(Unit) {
viewModel.collectPopulateBouquets()
}
if (viewModel.apiStatus == APIStatus.ERROR) {
ErrorPlaceholderWithoutButton(
message = viewModel.apiError
)
return
}
var bouquetListUiState = viewModel.bouquetPopulateListUiState
when (viewModel.apiStatus) {
APIStatus.DONE -> {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "Хиты продаж",
fontFamily = FontFamily.Serif,
fontSize = 40.sp,
fontWeight = FontWeight.W600
)
Text(
text = "Топ-5 продаваемых букетов",
fontFamily = FontFamily.Serif,
fontSize = 20.sp,
fontWeight = FontWeight.W400
)
val padding = if (bouquetListUiState.size == 1) 95.dp else 0.dp
LazyVerticalGrid(
columns = GridCells.Fixed(if (bouquetListUiState.size == 1) 1 else 2),
contentPadding = PaddingValues(start = padding, end = padding)
) {
items(items = bouquetListUiState) { bouquet ->
BouquetCard(bouquet = bouquet)
}
}
}
}
APIStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholderWithoutButton(
message = viewModel.apiError
)
}
}

View File

@ -0,0 +1,279 @@
package com.example.flowershopapp.Entities.ComposeUI
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
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.flowershopapp.API.APIStatus
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.User.OrderViewModel
import com.example.flowershopapp.ComposeUI.User.AuthModel
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.ComposeUI.Bouquet.CartModel
import com.example.flowershopapp.ComposeUI.Bouquet.FavoriteModel
import com.example.flowershopapp.ComposeUI.Network.LoadingPlaceholder
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.R
import java.time.LocalDate
import java.time.format.DateTimeFormatter
@Composable
fun ShoppingCart(
navController: NavController,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val bouquets by CartModel.instance.bouquets.collectAsState()
val totalSum by CartModel.instance.totalSum.collectAsState()
var showDialog by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Корзина",
fontFamily = FontFamily.Serif,
fontSize = 40.sp,
fontWeight = FontWeight.W600
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround
) {
Text(text = "Общая сумма: ${totalSum}", fontSize = 25.sp, fontWeight = FontWeight.Bold)
Button(
onClick = {
val currentDate = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val formattedDate = currentDate.format(formatter)
val order = Order(
date = formattedDate,
sum = totalSum,
userId = AuthModel.currentUser.userId!!
)
viewModel.createOrder(order, bouquets.toMutableList())
showDialog = true
},
modifier = Modifier.padding(bottom = 20.dp),
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text("Оформить")
}
}
when (viewModel.apiStatus) {
APIStatus.DONE -> {
if (showDialog) {
AlertDialog(
onDismissRequest = {
CartModel.instance.clearBouquets()
showDialog = false
},
title = {
Text(text = "Заказ сделан!")
},
text = {
Text("Спасибо за покупку!")
},
confirmButton = {
Button(
onClick = {
CartModel.instance.clearBouquets()
showDialog = false
}
) {
Text("Ок")
}
}
)
}
}
APIStatus.LOADING -> LoadingPlaceholder()
else -> {
if (showDialog) {
AlertDialog(
onDismissRequest = {
CartModel.instance.clearBouquets()
showDialog = false
},
title = {
Text(text = "Ошибка соединения")
},
text = {
Text("Не удалось оформить заказ")
},
confirmButton = {
Button(
onClick = {
CartModel.instance.clearBouquets()
showDialog = false
}
) {
Text("Ок")
}
}
)
}
}
}
val padding = if (bouquets.size == 1) 95.dp else 0.dp
LazyVerticalGrid(
columns = GridCells.Fixed(if (bouquets.size == 1) 1 else 2),
contentPadding = PaddingValues(start = padding, end = padding),
modifier = Modifier.fillMaxSize()
) {
items(bouquets) { bouquet ->
CartBouquetCard(bouquet)
}
}
}
}
@Composable
fun CartBouquetCard(bouquetPair: Pair<Bouquet, Int>) {
val bouquet = bouquetPair.first
val countBouquet = bouquetPair.second
var heart by remember { mutableStateOf(FavoriteModel.instance.containsBouquet(bouquet)) }
var cart by remember { mutableStateOf(CartModel.instance.containsBouquet(bouquet)) }
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.wrapContentHeight(),
elevation = CardDefaults.cardElevation(
defaultElevation = 10.dp
),
colors = CardDefaults.cardColors(
containerColor = Color.White
)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box {
bouquet.image?.let { imageData ->
val decodedBitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
val imageBitmap = decodedBitmap.asImageBitmap()
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier
.height(190.dp)
.clip(shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)),
contentScale = ContentScale.FillBounds
)
}
Box(
Modifier
.padding(start = 3.dp, top = 3.dp)
.align(Alignment.TopStart)
) {
Icon(
painter = painterResource(id = cart),
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(shape = RoundedCornerShape(5.dp))
.clickable {
cart = CartModel.instance.updateList(bouquet)
}
)
}
Box(
Modifier
.padding(end = 3.dp, top = 3.dp)
.align(Alignment.TopEnd)
) {
Icon(
painter = painterResource(id = heart),
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(shape = RoundedCornerShape(5.dp))
.clickable {
heart = FavoriteModel.instance.addBouquet(bouquet)
}
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(text = bouquet.name, fontFamily = FontFamily.Serif, fontSize = 20.sp)
Text(
text = "${bouquet.quantityOfFlowers} цветов",
fontFamily = FontFamily.Serif,
fontSize = 15.sp
)
Text(text = "${bouquet.price}", fontFamily = FontFamily.Serif, fontSize = 15.sp)
Spacer(modifier = Modifier.height(5.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier.height(50.dp)
) {
Box(modifier = Modifier.fillMaxHeight(0.5f)) {
Icon(
painter = painterResource(id = R.drawable.icon_minus_cirlce),
contentDescription = "",
modifier = Modifier.clickable {
CartModel.instance.updateCount(bouquet, false)
}
)
}
Box {
Text(text = "${countBouquet}")
}
Box(modifier = Modifier.fillMaxHeight(0.5f)) {
Icon(
painter = painterResource(id = R.drawable.icon_add_circle),
contentDescription = "",
modifier = Modifier.clickable {
CartModel.instance.updateCount(bouquet, true)
}
)
}
}
}
}
}

View File

@ -0,0 +1,260 @@
package com.example.flowershopapp.ComposeUI.Navigation
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.flowershopapp.ComposeUI.Boot
import com.example.flowershopapp.ComposeUI.Bouquet.BouquetCatalog
import com.example.flowershopapp.ComposeUI.Bouquet.Favorite
import com.example.flowershopapp.ComposeUI.Bouquet.PopulateBouquets
import com.example.flowershopapp.ComposeUI.Order.OrderBouquets
import com.example.flowershopapp.ComposeUI.Order.Orders
import com.example.flowershopapp.ComposeUI.User.Login
import com.example.flowershopapp.ComposeUI.User.Profile
import com.example.flowershopapp.ComposeUI.User.Signup
import com.example.flowershopapp.ComposeUI.User.Statistics
import com.example.flowershopapp.Entities.ComposeUI.ShoppingCart
import com.example.flowershopapp.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Topbar(
navController: NavHostController,
currentScreen: Screen?
) {
var showImage by remember { mutableStateOf(true) }
TopAppBar(
title = {
if (showImage) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Column(horizontalAlignment = Alignment.End) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = "ivi",
Modifier
.padding(end = 14.dp)
.size(180.dp)
)
}
}
}
},
navigationIcon = {
if (
navController.previousBackStackEntry != null
&& (currentScreen == null || !currentScreen.showInBottomBar)
) {
showImage = false
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimary
)
}
} else {
showImage = true
}
}
)
}
@Composable
fun Navbar1(
navController: NavHostController,
currentDestination: NavDestination?,
modifier: Modifier = Modifier
) {
NavigationBar(
modifier = modifier
.height(55.dp)
.clip(RoundedCornerShape(20.dp))
) {
Screen.bottomBarItems.forEach { screen ->
NavigationBarItem(
colors = NavigationBarItemDefaults.colors(
indicatorColor = Color.LightGray
),
modifier = modifier.padding(10.dp),
icon = {
Icon(
painter = painterResource(screen.icon!!),
contentDescription = null
)
},
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
@Composable
fun Navbar(
navController: NavHostController,
currentDestination: NavDestination?,
modifier: Modifier = Modifier
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(95.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
Screen.bottomBarItems.forEach { screen ->
val isSelected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
val contentColor =
if (isSelected) MaterialTheme.colorScheme.primary else Color.Gray.copy(alpha = 0.8f)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(15.dp))
.clickable(onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
})
) {
Column(
modifier = Modifier
.padding(12.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
screen.icon?.let {
Icon(
modifier = Modifier.size(25.dp),
painter = painterResource(screen.icon),
contentDescription = null,
tint = contentColor
)
}
}
}
}
}
}
@Composable
fun Navhost(
navController: NavHostController,
innerPadding: PaddingValues,
startScreen: String,
modifier: Modifier = Modifier
) {
NavHost(
navController,
startDestination = startScreen,
modifier.padding(innerPadding)
) {
composable(Screen.Signup.route) { Signup(navController) }
composable(Screen.Login.route) { Login(navController) }
composable(Screen.Boot.route) { Boot(navController) }
composable(Screen.BouquetCatalog.route) { BouquetCatalog(navController) }
composable(Screen.PopulateBouquets.route) { PopulateBouquets(navController) }
composable(Screen.Favorite.route) { Favorite(navController) }
composable(Screen.ShoppingCart.route) { ShoppingCart(navController) }
composable(Screen.Profile.route) { Profile(navController) }
composable(Screen.Statistics.route) { Statistics(navController) }
composable(
Screen.Orders.route
) { backStackEntry ->
backStackEntry.arguments?.let { Orders(navController) }
}
composable(
Screen.OrderBouquets.route,
arguments = listOf(navArgument("id") {
type = NavType.IntType
})
)
{
OrderBouquets(navController)
}
}
}
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun MainNavbar() {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val currentScreen = currentDestination?.route?.let { Screen.getItem(it) }
Scaffold(modifier = Modifier,
topBar = {
if (currentScreen == null || (currentScreen.showInBottomBar && currentScreen.showTopBarAndNavBar)) {
Topbar(navController, currentScreen)
}
},
bottomBar = {
if (currentScreen == null || (currentScreen.showInBottomBar && currentScreen.showTopBarAndNavBar)) {
Navbar(navController, currentDestination)
}
}
) { innerPadding ->
Navhost(navController, innerPadding, Screen.Login.route)
}
}

View File

@ -0,0 +1,70 @@
package com.example.flowershopapp.ComposeUI.Navigation
import androidx.annotation.StringRes
import com.example.flowershopapp.R
enum class Screen(
val route: String,
@StringRes val resourceId: Int,
val icon: Int?,
val showInBottomBar: Boolean = true,
val showTopBarAndNavBar: Boolean = true
) {
Signup(
"signup",
R.string.signup_title,
null,
showInBottomBar = false,
showTopBarAndNavBar = false
),
Login(
"login",
R.string.login_title,
null,
showInBottomBar = false,
showTopBarAndNavBar = false
),
Boot("boot", R.string.boot_title, null, showInBottomBar = false, showTopBarAndNavBar = false),
Orders(
"orders",
R.string.boot_title,
null,
showInBottomBar = false,
showTopBarAndNavBar = false
),
OrderBouquets(
"bouquets/{id}",
R.string.boot_title,
null,
showInBottomBar = false,
showTopBarAndNavBar = false
),
BouquetCatalog("bouquet-catalog", R.string.bouquet_catalog_title, R.drawable.icons8_home),
ShoppingCart("shopping-cart", R.string.shoppingCart_title, R.drawable.icons8_cart),
Profile("profile", R.string.profile_title, R.drawable.icons8_user),
Favorite("favorite", R.string.favorite_title, R.drawable.icons8_favorite),
Statistics(
"statistics",
R.string.favorite_title,
R.drawable.icons8_favorite,
showInBottomBar = false,
showTopBarAndNavBar = false
),
PopulateBouquets("populate-bouquets", R.string.populate_title, R.drawable.fire);
companion object {
val bottomBarItems = listOf(
BouquetCatalog,
PopulateBouquets,
Favorite,
ShoppingCart,
Profile
)
fun getItem(route: String): Screen? {
val findRoute = route.split("/").first()
return values().find { value -> value.route.startsWith(findRoute) }
}
}
}

View File

@ -0,0 +1,83 @@
package com.example.flowershopapp.ComposeUI.Network
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.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.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
@Composable
fun ErrorPlaceholder(message: String, onBack: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = TextUnit(value = 20F, type = TextUnitType.Sp),
text = message,
color = Color(0xFFFF1744)
)
Spacer(modifier = Modifier.padding(bottom = 10.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onBack()
}
) {
Text("Назад")
}
}
}
@Composable
fun ErrorPlaceholderWithoutButton(message: String) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = TextUnit(value = 20F, type = TextUnitType.Sp),
text = message,
color = Color(0xFFFF1744)
)
}
}
@Composable
fun LoadingPlaceholder() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = TextUnit(value = 25F, type = TextUnitType.Sp),
text = "Загрузка"
)
}
}

View File

@ -0,0 +1,47 @@
package com.example.flowershopapp.ComposeUI.Network
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.flowershopapp.API.APIStatus
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
open class NetworkViewModel : ViewModel() {
var apiStatus by mutableStateOf(APIStatus.DONE)
private set
var apiError by mutableStateOf("")
private set
fun runInScope(
actionSuccess: suspend () -> Unit,
actionError: suspend () -> Unit
) {
viewModelScope.launch {
apiStatus = APIStatus.LOADING
runCatching {
actionSuccess()
apiStatus = APIStatus.DONE
apiError = ""
}.onFailure { e: Throwable ->
when (e) {
is IOException,
is HttpException -> {
actionError()
apiStatus = APIStatus.ERROR
apiError = e.localizedMessage ?: e.toString()
}
else -> throw e
}
}
}
}
fun runInScope(actionSuccess: suspend () -> Unit) {
runInScope(actionSuccess, actionError = {})
}
}

View File

@ -0,0 +1,187 @@
package com.example.flowershopapp.ComposeUI.Order
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
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.flowershopapp.API.APIStatus
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.Navigation.Screen
import com.example.flowershopapp.ComposeUI.Network.ErrorPlaceholder
import com.example.flowershopapp.ComposeUI.Network.LoadingPlaceholder
import com.example.flowershopapp.ComposeUI.User.OrderViewModel
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.ComposeUI.Bouquet.CartModel
import com.example.flowershopapp.ComposeUI.Bouquet.FavoriteModel
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun OrderBouquets(
navController: NavController,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
if (viewModel.apiStatus == APIStatus.ERROR) {
ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Profile.route) }
)
return
}
var bouquetListUiState = viewModel.orderBouquetListUiState.collectAsState(initial = emptyList())
val padding = if (bouquetListUiState.value.size == 1) 95.dp else 0.dp
when (viewModel.apiStatus) {
APIStatus.DONE -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Букеты",
fontFamily = FontFamily.Serif,
fontSize = 40.sp,
fontWeight = FontWeight.W600
)
LazyVerticalGrid(
columns = GridCells.Fixed(if (bouquetListUiState.value.size == 1) 1 else 2),
contentPadding = PaddingValues(start = padding, end = padding)
) {
bouquetListUiState.let {
items(
items = bouquetListUiState.value,
key = { it.first.bouquetId!! }) { bouquetPair ->
HistoryBouquetCard(bouquet = bouquetPair.first, bouquetPair.second)
}
}
}
}
}
APIStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Profile.route) }
)
}
}
@Composable
fun HistoryBouquetCard(bouquet: Bouquet, count: Int) {
var heart by remember { mutableStateOf(FavoriteModel.instance.containsBouquet(bouquet)) }
var cart by remember { mutableStateOf(CartModel.instance.containsBouquet(bouquet)) }
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.wrapContentHeight(),
elevation = CardDefaults.cardElevation(
defaultElevation = 10.dp
),
colors = CardDefaults.cardColors(
containerColor = Color.White
)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box {
bouquet.image?.let { imageData ->
val decodedBitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
val imageBitmap = decodedBitmap.asImageBitmap()
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier
.height(190.dp)
.clip(shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)),
contentScale = ContentScale.FillBounds
)
}
Box(
Modifier
.padding(start = 3.dp, top = 3.dp)
.align(Alignment.TopStart)
) {
Icon(
painter = painterResource(id = cart),
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(shape = RoundedCornerShape(5.dp))
.clickable {
cart = CartModel.instance.updateList(bouquet)
}
)
}
Box(
Modifier
.padding(end = 3.dp, top = 3.dp)
.align(Alignment.TopEnd)
) {
Icon(
painter = painterResource(id = heart),
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(shape = RoundedCornerShape(5.dp))
.clickable {
heart = FavoriteModel.instance.addBouquet(bouquet)
}
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(text = bouquet.name, fontFamily = FontFamily.Serif, fontSize = 20.sp)
Text(
text = "${bouquet.quantityOfFlowers} цветов",
fontFamily = FontFamily.Serif,
fontSize = 15.sp
)
Text(text = "${bouquet.price}", fontFamily = FontFamily.Serif, fontSize = 15.sp)
Box(
modifier = Modifier
.padding(0.dp)
.clip(RoundedCornerShape(10.dp))
.fillMaxWidth()
.height(40.dp),
contentAlignment = Alignment.Center
) {
Text(text = "Куплено ${count} шт.")
}
}
}
}

View File

@ -0,0 +1,111 @@
package com.example.flowershopapp.ComposeUI.User
import androidx.lifecycle.SavedStateHandle
import androidx.paging.PagingData
import com.example.flowershopapp.ComposeUI.Network.NetworkViewModel
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
import com.example.flowershopapp.Entities.Model.OrderByDate
import com.example.flowershopapp.Entities.Model.OrdersWithBouquets
import com.example.flowershopapp.Entities.Repository.Order.OrderRepository
import com.example.flowershopapp.Entities.Repository.OrderBouquets.OrdersWithBouquetsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
class OrderViewModel(
savedStateHandle: SavedStateHandle,
private val orderRepository: OrderRepository,
private val orderBouquetRepository: OrdersWithBouquetsRepository,
) : NetworkViewModel() {
private val userId: Int = AuthModel.currentUser.userId!!
private val orderId: Int = savedStateHandle["id"] ?: -1
var ordersListUiState: Flow<PagingData<Order>> =
MutableStateFlow<PagingData<Order>>(PagingData.empty())
private set
private val _orderBouquetListUiState = MutableStateFlow<List<Pair<Bouquet, Int>>>(emptyList())
val orderBouquetListUiState: Flow<List<Pair<Bouquet, Int>>> =
_orderBouquetListUiState.asStateFlow()
private val _orderByDateListUiState = MutableStateFlow<OrderByDate>(OrderByDate(listOf(), 0, 0))
val orderByDateListUiState: Flow<OrderByDate> = _orderByDateListUiState.asStateFlow()
init {
runInScope(
actionSuccess = {
ordersListUiState = orderRepository.getOrdersByUser(userId)
}
)
loadBouquetsByOrder()
}
private fun loadBouquetsByOrder() {
runInScope(
actionSuccess = {
if (orderId != -1) {
orderRepository.getBouquetsByOrder(orderId).collect { bouquets ->
withContext(Dispatchers.IO) {
val orderBouquet = orderBouquetRepository.getAll()
val orderBouquetMap = orderBouquet.associateBy { it.bouquetId }
_orderBouquetListUiState.value = bouquets.map { bouquet ->
val count = orderBouquetMap[bouquet.bouquetId]?.count ?: 0
Pair(bouquet, count)
}
}
}
}
},
actionError = {
_orderBouquetListUiState.value = emptyList()
}
)
}
fun loadStatistics(startDate: String, endDate: String) {
runInScope(
actionSuccess = {
_orderByDateListUiState.value =
orderRepository.getOrdersByDate(userId, startDate, endDate).first()
},
actionError = {
_orderByDateListUiState.value = OrderByDate(emptyList(), 0, 0)
}
)
}
fun createOrder(order: Order, bouquetsPair: List<Pair<Bouquet, Int>>) {
runInScope(
actionSuccess = {
val createdOrder = orderRepository.insertWithReturn(order)
bouquetsPair.forEach { bouquetPair ->
orderBouquetRepository.insert(
OrderBouquetCrossRef(
createdOrder.orderId!!,
bouquetPair.first.bouquetId!!,
bouquetPair.second
)
)
}
}
)
}
}
data class UserWithOrdersUiState(
val userDetails: UserWithOrdersDetails = UserWithOrdersDetails()
)
data class UserWithOrdersDetails(
val orders: List<OrdersWithBouquets> = listOf()
)
fun List<OrdersWithBouquets>.toDetails(): UserWithOrdersDetails = UserWithOrdersDetails(
orders = this,
)
fun List<OrdersWithBouquets>.toUiState(): UserWithOrdersUiState = UserWithOrdersUiState(
userDetails = this.toDetails()
)

View File

@ -0,0 +1,104 @@
package com.example.flowershopapp.ComposeUI.Order
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.Navigation.Screen
import com.example.flowershopapp.ComposeUI.User.OrderViewModel
import com.example.flowershopapp.Entities.Model.Order
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Orders(
navController: NavController,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val ordersListUiState = viewModel.ordersListUiState.collectAsLazyPagingItems()
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
ordersListUiState.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
LazyColumn(
modifier = Modifier
.pullRefresh(state)
.padding(top = 16.dp)
) {
items(
count = ordersListUiState.itemCount,
key = ordersListUiState.itemKey(),
contentType = ordersListUiState.itemContentType()
) { index ->
val order = ordersListUiState[index]
order?.let {
OrderItem(order) {
navController.navigate(
Screen.OrderBouquets.route.replace(
"{id}",
order.orderId.toString()
)
)
}
}
}
}
}
@Composable
fun OrderItem(order: Order, action: () -> Unit) {
Card(
colors = CardDefaults.cardColors(
containerColor = Color.White
),
shape = RoundedCornerShape(8.dp),
elevation = CardDefaults.cardElevation(
defaultElevation = 10.dp,
focusedElevation = 20.dp
),
modifier = Modifier
.fillMaxWidth()
.padding(top = 5.dp, bottom = 20.dp, start = 30.dp, end = 30.dp)
.clickable {
action()
},
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Номер заказа: ${order.orderId}",
fontWeight = FontWeight.Bold
)
Text(text = "Дата: ${order.date}")
Text(text = "Сумма: ${order.sum}")
}
}
}

View File

@ -0,0 +1,15 @@
package com.example.flowershopapp.ComposeUI.User
import androidx.lifecycle.ViewModel
import com.example.flowershopapp.Entities.Model.User
class AuthModel : ViewModel() {
companion object {
lateinit var currentUser: User
private set
}
fun setAuthenticatedUser(user: User?) {
currentUser = user ?: throw IllegalArgumentException("User cannot be null")
}
}

View File

@ -0,0 +1,189 @@
package com.example.flowershopapp.ComposeUI.User
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
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.flowershopapp.API.APIStatus
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.Navigation.Screen
import com.example.flowershopapp.ComposeUI.Network.ErrorPlaceholder
import com.example.flowershopapp.ComposeUI.Network.LoadingPlaceholder
import com.example.flowershopapp.Entities.Model.User
import com.example.flowershopapp.R
@Composable
fun Login(
navController: NavController,
viewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
if (viewModel.apiStatus == APIStatus.ERROR) {
ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Login.route) }
)
return
}
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var usernameError by remember { mutableStateOf<String?>(null) }
var passwordError by remember { mutableStateOf<String?>(null) }
val authModel: AuthModel = viewModel()
fun validateInput(): Boolean {
var isValid = true
if (username.isBlank()) {
usernameError = "Введите имя пользователя"
isValid = false
} else {
usernameError = null
}
if (password.isBlank()) {
passwordError = "Введите пароль"
isValid = false
} else {
passwordError = null
}
return isValid
}
fun onAuthenticationSuccess(user: User) {
authModel.setAuthenticatedUser(user)
navController.navigate(Screen.BouquetCatalog.route)
}
fun login() {
if (validateInput()) {
viewModel.loginUser(username, password) { user ->
if (user != null) {
onAuthenticationSuccess(user)
} else {
}
}
}
}
when (viewModel.apiStatus) {
APIStatus.DONE -> {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = colorResource(id = R.color.backgroundWindow)),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = "",
Modifier.size(180.dp)
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.weight(2f)
.padding(bottom = 10.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.login_title),
fontSize = 26.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = username,
onValueChange = { username = it },
label = { Text("Имя пользователя") },
isError = usernameError != null,
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
if (usernameError != null) {
Text(text = usernameError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Пароль") },
isError = passwordError != null,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
if (usernameError != null) {
Text(text = passwordError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(16.dp))
Button(
shape = RoundedCornerShape(5.dp), onClick = { login() },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text("Вход")
}
Spacer(modifier = Modifier.height(16.dp))
TextButton(onClick = { navController.navigate(Screen.Signup.route) }) {
Text(text = "У меня нет учетной записи")
}
}
}
}
APIStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Login.route) }
)
}
}

View File

@ -0,0 +1,136 @@
package com.example.flowershopapp.ComposeUI.User
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
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 androidx.navigation.NavController
import com.example.flowershopapp.ComposeUI.Navigation.Screen
import com.example.flowershopapp.Entities.Model.User
import com.example.flowershopapp.R
@Composable
fun Profile(navController: NavController) {
var phoneNumber by remember { mutableStateOf("Ваш номер телефона") }
var userName by remember { mutableStateOf("Имя пользователя") }
var dateOfBirth by remember { mutableStateOf("01-01-2000") }
val (user, setUser) = remember { mutableStateOf<User?>(null) }
LaunchedEffect(Unit) {
setUser(AuthModel.currentUser)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(color = colorResource(id = R.color.backgroundWindow)),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
user?.let {
Column(
modifier = Modifier
.fillMaxWidth()
.weight(2f)
.padding(bottom = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Профиль",
fontSize = 26.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
Image(
painter = painterResource(id = R.drawable.icons8_profile),
contentDescription = "",
Modifier.size(140.dp)
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = user.userName,
onValueChange = { userName = it },
label = { Text("Имя пользователя") },
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = user.dateOfBirth,
onValueChange = { dateOfBirth = it },
label = { Text("Дата рождения") },
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = user.phoneNumber,
onValueChange = { phoneNumber = it },
label = { Text("Номер телефона") },
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
Spacer(modifier = Modifier.height(16.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 90.dp, end = 90.dp),
shape = RoundedCornerShape(5.dp),
onClick = { navController.navigate(Screen.Orders.route) },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text("История заказов")
}
Spacer(modifier = Modifier.height(8.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 90.dp, end = 90.dp),
shape = RoundedCornerShape(5.dp),
onClick = { navController.navigate(Screen.Statistics.route) },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text("Статистика")
}
}
}
}
}

View File

@ -0,0 +1,241 @@
package com.example.flowershopapp.ComposeUI.User
import android.app.DatePickerDialog
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
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.flowershopapp.API.APIStatus
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.Navigation.Screen
import com.example.flowershopapp.ComposeUI.Network.ErrorPlaceholder
import com.example.flowershopapp.ComposeUI.Network.LoadingPlaceholder
import com.example.flowershopapp.R
import java.util.Calendar
@Composable
fun Signup(
navController: NavController,
viewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
if (viewModel.apiStatus == APIStatus.ERROR) {
ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Signup.route) }
)
return
}
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var dateOfBirth by remember { mutableStateOf("Дата рождения") }
var phoneNumber by remember { mutableStateOf("") }
var usernameError by remember { mutableStateOf<String?>(null) }
var passwordError by remember { mutableStateOf<String?>(null) }
var dateOfBirthError by remember { mutableStateOf<String?>(null) }
var phoneNumberError by remember { mutableStateOf<String?>(null) }
var width by remember { mutableStateOf(0.dp) }
val context = LocalContext.current
val calendar = Calendar.getInstance()
val dateOfBirthDialog = DatePickerDialog(
context,
{ _, year, month, dayOfMonth ->
dateOfBirth = "$year-${month + 1}-$dayOfMonth"
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
fun validateInput(): Boolean {
var isValid = true
if (username.isBlank()) {
usernameError = "Введите имя пользователя"
isValid = false
} else {
usernameError = null
}
if (password.isBlank()) {
passwordError = "Введите пароль"
isValid = false
} else {
passwordError = null
}
if (dateOfBirth.isBlank()) {
dateOfBirthError = "Введите дату рождения"
isValid = false
} else {
dateOfBirthError = null
}
if (phoneNumber.isBlank()) {
phoneNumberError = "Введите номер телефона"
isValid = false
} else {
phoneNumberError = null
}
return isValid
}
fun register() {
if (validateInput()) {
viewModel.registerUser(username, password, dateOfBirth, phoneNumber) {
navController.navigate(Screen.Login.route)
}
}
}
when (viewModel.apiStatus) {
APIStatus.DONE -> {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = colorResource(id = R.color.backgroundWindow)),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = "",
Modifier.size(180.dp)
)
}
Column(
modifier = Modifier
.wrapContentWidth()
.weight(2f)
.padding(bottom = 10.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.signup_title),
fontSize = 26.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = username,
onValueChange = { username = it },
label = { Text("Имя пользователя") },
isError = usernameError != null,
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
if (usernameError != null) {
Text(text = usernameError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = phoneNumber,
onValueChange = { phoneNumber = it },
label = { Text("Номер телефона") },
isError = phoneNumberError != null,
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
if (phoneNumberError != null) {
Text(text = phoneNumberError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 55.dp, end = 55.dp)
.background(colorResource(id = R.color.textFieldContainer))
.clickable { dateOfBirthDialog.show() },
) {
Text(modifier = Modifier
.padding(start = 20.dp),
text = dateOfBirth, fontSize = 16.sp, color = Color.DarkGray)
}
if (dateOfBirthError != null) {
Text(text = dateOfBirthError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Пароль") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.textFieldContainer),
focusedContainerColor = colorResource(id = R.color.textFieldContainer)
)
)
Spacer(modifier = Modifier.height(16.dp))
Button(
shape = RoundedCornerShape(5.dp),
onClick = { register() },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text("Создать")
}
Spacer(modifier = Modifier.height(16.dp))
TextButton(onClick = { navController.navigate(Screen.Login.route) }) {
Text(text = "У меня есть учетная запись")
}
}
}
}
APIStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { }
)
}
}

View File

@ -0,0 +1,221 @@
package com.example.flowershopapp.ComposeUI.User
import android.app.DatePickerDialog
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
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.flowershopapp.API.APIStatus
import com.example.flowershopapp.ComposeUI.AppViewModelProvider
import com.example.flowershopapp.ComposeUI.Navigation.Screen
import com.example.flowershopapp.ComposeUI.Network.ErrorPlaceholder
import com.example.flowershopapp.ComposeUI.Network.LoadingPlaceholder
import com.example.flowershopapp.ComposeUI.Order.OrderItem
import com.example.flowershopapp.Entities.Model.OrderByDate
import com.example.flowershopapp.R
import java.util.Calendar
@Composable
fun Statistics(
navController: NavController,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
if (viewModel.apiStatus == APIStatus.ERROR) {
ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Profile.route) }
)
return
}
var startDateError by remember { mutableStateOf<String?>(null) }
var endDateError by remember { mutableStateOf<String?>(null) }
val orderListUiState =
viewModel.orderByDateListUiState.collectAsState(OrderByDate(listOf(), 0, 0))
val context = LocalContext.current
val calendar = Calendar.getInstance()
var startDate by remember { mutableStateOf("Выберите начальную дату") }
val startDateDialog = DatePickerDialog(
context,
{ _, year, month, dayOfMonth ->
startDate = "$year-${month + 1}-$dayOfMonth"
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
var endDate by remember { mutableStateOf("Выберите конечную дату") }
val endDateDialog = DatePickerDialog(
context,
{ _, year, month, dayOfMonth ->
endDate = "$year-${month + 1}-$dayOfMonth"
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
fun validateInput(): Boolean {
var isValid = true
if (startDate.isBlank()) {
startDateError = "Введите начальную дату"
isValid = false
} else {
startDateError = null
}
if (endDate.isBlank()) {
endDateError = "Введите конечную дату"
isValid = false
} else {
endDateError = null
}
return isValid
}
fun getStatistics() {
if (validateInput()) {
viewModel.loadStatistics(startDate, endDate)
}
}
when (viewModel.apiStatus) {
APIStatus.DONE -> {
Column(
modifier = Modifier
.background(color = colorResource(id = R.color.backgroundWindow)),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp, bottom = 10.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Статистика по заказам",
fontSize = 32.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 50.dp, end = 50.dp),
shape = RoundedCornerShape(5.dp),
onClick = { startDateDialog.show() },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text(startDate)
}
if (startDateError != null) {
Text(text = startDateError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(4.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 50.dp, end = 50.dp),
shape = RoundedCornerShape(5.dp),
onClick = { endDateDialog.show() },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text(endDate)
}
if (endDateError != null) {
Text(text = endDateError!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(16.dp))
Button(
shape = RoundedCornerShape(5.dp), onClick = { getStatistics() },
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(id = R.color.button)
)
) {
Text("Получить данные")
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp, bottom = 10.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Всего заказов: ${orderListUiState.value.orderCount}",
fontSize = 20.sp,
fontWeight = FontWeight.Medium
)
Text(
text = "Общая сумма: ${orderListUiState.value.totalSum}",
fontSize = 20.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
}
}
Text(
text = "Заказы",
fontSize = 32.sp,
fontWeight = FontWeight.Medium
)
LazyColumn(
modifier = Modifier
.padding(top = 16.dp)
) {
items(
items = orderListUiState.value.orders
) { order ->
OrderItem(order) {
navController.navigate(
Screen.OrderBouquets.route.replace(
"{id}",
order.orderId.toString()
)
)
}
}
}
}
}
APIStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = { navController.navigate(Screen.Profile.route) }
)
}
}

View File

@ -0,0 +1,74 @@
package com.example.flowershopapp.ComposeUI.User
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.example.flowershopapp.ComposeUI.Network.NetworkViewModel
import com.example.flowershopapp.Entities.Model.User
import com.example.flowershopapp.Entities.Repository.User.UserRepository
class UserViewModel(
private val userRepository: UserRepository
) : NetworkViewModel() {
var userUiState by mutableStateOf(UserUiState())
private set
fun loginUser(username: String, password: String, onResult: (User?) -> Unit) {
runInScope(
actionSuccess = {
userRepository.getAll()
val user = userRepository.getUserByName(username)
if (user.password == password) {
onResult(user)
} else {
onResult(null)
}
}
)
}
fun registerUser(
username: String,
password: String,
dateOfBirth: String,
phoneNumber: String,
relocate: () -> Unit
) {
runInScope(
actionSuccess = {
val user = userRepository.insert(User(username, dateOfBirth, phoneNumber, password))
relocate()
}
)
}
}
data class UserUiState(
val userDetails: UserDetails = UserDetails()
)
data class UserDetails(
val user: User? = null,
)
data class AuthenticationState(
val isLoggedIn: Boolean = false,
val errorMessage: String? = null
)
fun User.toDetails(): UserDetails = UserDetails(
user = User(userId, userName, dateOfBirth, phoneNumber, password)
)
fun User.toUiState(): UserUiState = UserUiState(
userDetails = this.toDetails()
)
fun UserUiState.toUser(): User = User(
userId = userDetails.user!!.userId,
userName = userDetails.user.userName,
dateOfBirth = userDetails.user.dateOfBirth,
phoneNumber = userDetails.user.phoneNumber,
password = userDetails.user.password
)

View File

@ -0,0 +1,74 @@
package com.example.flowershopapp.Database
import android.content.Context
import com.example.flowershopapp.API.MyServerService
import com.example.flowershopapp.API.Repository.RestBouquetRepository
import com.example.flowershopapp.API.Repository.RestOrderBouquetRepository
import com.example.flowershopapp.API.Repository.RestOrderRepository
import com.example.flowershopapp.API.Repository.RestUserRepository
import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository
import com.example.flowershopapp.Entities.Repository.Bouquet.OfflineBouquetRepository
import com.example.flowershopapp.Entities.Repository.Order.OfflineOrderRepository
import com.example.flowershopapp.Entities.Repository.OrderBouquets.OfflineOrdersWithBouquetsRepository
import com.example.flowershopapp.Entities.Repository.User.OfflineUserRepository
interface AppContainer {
val bouquetRestRepository: RestBouquetRepository
val userRestRepository: RestUserRepository
val orderRestRepository: RestOrderRepository
val orderBouquetRestRepository: RestOrderBouquetRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 5
}
}
class AppDataContainer(private val context: Context) : AppContainer {
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val bouquetRepository: OfflineBouquetRepository by lazy {
OfflineBouquetRepository(AppDatabase.getInstance(context).bouquetDao())
}
private val orderRepository: OfflineOrderRepository by lazy {
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
}
private val orderBouquetsRepository: OfflineOrdersWithBouquetsRepository by lazy {
OfflineOrdersWithBouquetsRepository(AppDatabase.getInstance(context).orderWithBouquetsDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDAO())
}
override val orderBouquetRestRepository: RestOrderBouquetRepository by lazy {
RestOrderBouquetRepository(
MyServerService.getInstance(),
orderBouquetsRepository
)
}
override val orderRestRepository: RestOrderRepository by lazy {
RestOrderRepository(
MyServerService.getInstance(),
orderRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val bouquetRestRepository: RestBouquetRepository by lazy {
RestBouquetRepository(
MyServerService.getInstance(),
bouquetRepository,
orderBouquetsRepository,
remoteKeyRepository,
orderBouquetRestRepository,
orderRestRepository,
AppDatabase.getInstance(context)
)
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
MyServerService.getInstance(),
userRepository
)
}
}

View File

@ -0,0 +1,53 @@
package com.example.flowershopapp.Database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.flowershopapp.Database.RemoteKeys.DAO.RemoteKeysDAO
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys
import com.example.flowershopapp.Entities.DAO.BouquetDAO
import com.example.flowershopapp.Entities.DAO.OrderDAO
import com.example.flowershopapp.Entities.DAO.OrdersWithBouquet
import com.example.flowershopapp.Entities.DAO.UserDAO
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
import com.example.flowershopapp.Entities.Model.User
@Database(
entities = [User::class, Order::class, Bouquet::class, OrderBouquetCrossRef::class, RemoteKeys::class],
version = 6,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDAO
abstract fun bouquetDao(): BouquetDAO
abstract fun orderDao(): OrderDAO
abstract fun orderWithBouquetsDao(): OrdersWithBouquet
abstract fun remoteKeysDAO(): RemoteKeysDAO
companion object {
private const val DB_NAME: String = "flowershop-db"
@Volatile
private var INSTANCE: AppDatabase? = null
private suspend fun populateDatabase(appContext: Context) {
INSTANCE?.let { database ->
}
}
fun getInstance(appContext: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
AppDatabase::class.java,
DB_NAME
)
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.example.flowershopapp.Database.RemoteKeys.DAO
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys
@Dao
interface RemoteKeysDAO {
@Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type")
suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys?>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -0,0 +1,30 @@
package com.example.flowershopapp.Database.RemoteKeys.Model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.User
enum class RemoteKeyType(private val type: String) {
BOUQUET(Bouquet::class.simpleName ?: "Bouquet"),
ORDER(Order::class.simpleName ?: "Order"),
USER(User::class.simpleName ?: "User");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -0,0 +1,16 @@
package com.example.flowershopapp.Database.RemoteKeys.Repository
import com.example.flowershopapp.Database.RemoteKeys.DAO.RemoteKeysDAO
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys
class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDAO) : RemoteKeyRepository {
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
remoteKeysDao.getRemoteKeys(id, type)
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys?>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -0,0 +1,10 @@
package com.example.flowershopapp.Database.RemoteKeys.Repository
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType
import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys
interface RemoteKeyRepository {
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys?>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -0,0 +1,30 @@
package com.example.flowershopapp.Entities.DAO
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.flowershopapp.Entities.Model.Bouquet
@Dao
interface BouquetDAO {
@Query("select * from bouquets")
fun getAll(): PagingSource<Int, Bouquet>
@Query("select * from bouquets where bouquetId = :id")
fun getBouquet(id: Int): Bouquet
@Insert
suspend fun insert(vararg bouquet: Bouquet)
@Update
suspend fun update(bouquet: Bouquet)
@Delete
suspend fun delete(bouquet: Bouquet)
@Query("DELETE FROM bouquets")
suspend fun deleteAll()
}

View File

@ -0,0 +1,49 @@
package com.example.flowershopapp.Entities.DAO
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.OrdersWithBouquets
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDAO {
@Query(
"""
SELECT orders.* FROM orders
WHERE userId = :userId
"""
)
fun getOrdersByUser(userId: Int): PagingSource<Int, Order>
@Query(
"""
SELECT bouquets.* FROM bouquets
INNER JOIN orderbouquetcrossref ON bouquets.bouquetId = orderbouquetcrossref.bouquetId
WHERE orderbouquetcrossref.orderId = :orderId
"""
)
fun getBouquetsByOrder(orderId: Int): Flow<List<Bouquet>>
@Query("select * from orders")
fun getOrdersWithBouquet(): Flow<List<OrdersWithBouquets>>
@Query("select * from orders where orderId = :id")
fun getOrderWithBouquet(id: Int): OrdersWithBouquets
@Insert
suspend fun insert(vararg order: Order)
@Delete
suspend fun delete(order: Order)
@Query("DELETE FROM orders")
suspend fun deleteAll()
@Query("select * from orders where orderId = :id")
suspend fun getById(id: Int): Order
}

View File

@ -0,0 +1,22 @@
package com.example.flowershopapp.Entities.DAO
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
@Dao
interface OrdersWithBouquet {
@Query("select * from orderbouquetcrossref")
fun getAll(): List<OrderBouquetCrossRef>
@Insert
suspend fun insert(vararg order: OrderBouquetCrossRef)
@Delete
suspend fun delete(order: OrderBouquetCrossRef)
@Query("DELETE FROM orderbouquetcrossref")
suspend fun deleteAll()
}

View File

@ -0,0 +1,29 @@
package com.example.flowershopapp.Entities.DAO
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.flowershopapp.Entities.Model.User
@Dao
interface UserDAO {
@Query("select * from users")
fun getAll(): List<User>
@Query("select * from users where name = :userName")
fun getUserByName(userName: String): User
@Insert
suspend fun insert(vararg user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("DELETE FROM users")
suspend fun deleteAll()
}

View File

@ -0,0 +1,39 @@
package com.example.flowershopapp.Entities.Model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(tableName = "bouquets")
data class Bouquet(
@PrimaryKey(autoGenerate = true)
val bouquetId: Int?,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "quantityOfFlowers")
val quantityOfFlowers: Int,
@ColumnInfo(name = "duration")
val price: Int,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB, name = "image")
val image: ByteArray? = null,
) {
@Ignore
constructor(
name: String,
quantityOfFlowers: Int,
price: Int,
image: ByteArray,
) : this(null, name, quantityOfFlowers, price, image)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Bouquet
return bouquetId == other.bouquetId
}
override fun hashCode(): Int {
return bouquetId ?: -1
}
}

View File

@ -0,0 +1,46 @@
package com.example.flowershopapp.Entities.Model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "orders",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = arrayOf("userId"),
childColumns = arrayOf("userId"),
onDelete = ForeignKey.CASCADE
)],
indices = [Index(value = ["userId"])]
)
data class Order(
@PrimaryKey(autoGenerate = true)
val orderId: Int?,
@ColumnInfo(name = "date")
val date: String,
@ColumnInfo(name = "sum")
val sum: Int,
val userId: Int,
) {
@Ignore
constructor(
userId: Int,
date: String,
sum: Int,
) : this(null, date, sum, userId)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Order
return orderId == other.orderId
}
override fun hashCode(): Int {
return orderId ?: -1
}
}

View File

@ -0,0 +1,10 @@
package com.example.flowershopapp.Entities.Model
import androidx.room.Entity
@Entity(primaryKeys = ["orderId", "bouquetId"])
class OrderBouquetCrossRef(
val orderId: Int,
val bouquetId: Int,
val count: Int
)

View File

@ -0,0 +1,25 @@
package com.example.flowershopapp.Entities.Model
data class OrderByDate(
val orders: List<Order>,
val orderCount: Int,
val totalSum: Int,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as OrderByDate
if (orders != other.orders) return false
if (orderCount != other.orderCount) return false
return totalSum == other.totalSum
}
override fun hashCode(): Int {
var result = orders.hashCode()
result = 31 * result + orderCount
result = 31 * result + totalSum
return result
}
}

View File

@ -0,0 +1,15 @@
package com.example.flowershopapp.Entities.Model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class OrdersWithBouquets(
@Embedded val order: Order,
@Relation(
parentColumn = "orderId",
entityColumn = "bouquetId",
associateBy = Junction(OrderBouquetCrossRef::class)
)
val bouquets: List<Bouquet>
)

View File

@ -0,0 +1,51 @@
package com.example.flowershopapp.Entities.Model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val userId: Int?,
@ColumnInfo(name = "name")
val userName: String,
@ColumnInfo(name = "dateOfBirth")
val dateOfBirth: String,
@ColumnInfo(name = "phoneNumber")
val phoneNumber: String,
@ColumnInfo(name = "password")
val password: String
) {
@Ignore
constructor(
userName: String,
dateOfBirth: String,
phoneNumber: String,
password: String
) : this(null, userName, dateOfBirth, phoneNumber, password)
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
if (userName != other.userName) return false
if (dateOfBirth != other.dateOfBirth) return false
if (phoneNumber != other.phoneNumber) return false
return password == other.password
}
override fun hashCode(): Int {
var result = userId ?: 0
result = 31 * result + userName.hashCode()
result = 31 * result + dateOfBirth.hashCode()
result = 31 * result + phoneNumber.hashCode()
result = 31 * result + password.hashCode()
return result
}
}

View File

@ -0,0 +1,14 @@
package com.example.flowershopapp.Entities.Repository.Bouquet
import androidx.paging.PagingData
import com.example.flowershopapp.Entities.Model.Bouquet
import kotlinx.coroutines.flow.Flow
interface BouquetRepository {
fun getAll(): Flow<PagingData<Bouquet>>
suspend fun getPopulateBouquets(): List<Bouquet>
suspend fun getBouquet(id: Int): Bouquet
suspend fun insert(bouquet: Bouquet)
suspend fun update(bouquet: Bouquet)
suspend fun delete(bouquet: Bouquet)
}

View File

@ -0,0 +1,33 @@
package com.example.flowershopapp.Entities.Repository.Bouquet
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.flowershopapp.Database.AppContainer
import com.example.flowershopapp.Entities.DAO.BouquetDAO
import com.example.flowershopapp.Entities.Model.Bouquet
import kotlinx.coroutines.flow.Flow
class OfflineBouquetRepository(private val bouquetDAO: BouquetDAO) : BouquetRepository {
override fun getAll(): Flow<PagingData<Bouquet>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = bouquetDAO::getAll
).flow
override suspend fun getPopulateBouquets(): List<Bouquet> {
TODO("Not yet implemented")
}
override suspend fun getBouquet(id: Int): Bouquet = bouquetDAO.getBouquet(id)
override suspend fun insert(bouquet: Bouquet) = bouquetDAO.insert(bouquet)
override suspend fun update(bouquet: Bouquet) = bouquetDAO.update(bouquet)
override suspend fun delete(bouquet: Bouquet) = bouquetDAO.delete(bouquet)
suspend fun deleteAll() = bouquetDAO.deleteAll()
fun getAllBouquetsPagingSource(): PagingSource<Int, Bouquet> = bouquetDAO.getAll()
suspend fun insertBouquets(bouquets: List<Bouquet>) =
bouquetDAO.insert(*bouquets.toTypedArray())
}

View File

@ -0,0 +1,47 @@
package com.example.flowershopapp.Entities.Repository.Order
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.flowershopapp.Database.AppContainer
import com.example.flowershopapp.Entities.DAO.OrderDAO
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.OrderByDate
import kotlinx.coroutines.flow.Flow
class OfflineOrderRepository(private val orderDAO: OrderDAO) : OrderRepository {
override fun getOrdersByUser(id: Int): Flow<PagingData<Order>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { orderDAO.getOrdersByUser(id) }
).flow
override suspend fun getBouquetsByOrder(orderId: Int): Flow<List<Bouquet>> =
orderDAO.getBouquetsByOrder(orderId)
override suspend fun insert(order: Order) = orderDAO.insert(order)
override suspend fun insertWithReturn(order: Order): Order {
TODO("Not yet implemented")
}
override suspend fun getOrdersByDate(
userId: Int,
starDate: String,
endDate: String
): Flow<OrderByDate> {
TODO("Not yet implemented")
}
override suspend fun delete(order: Order) = orderDAO.delete(order)
fun getOrdersByUserPagingSource(id: Int): PagingSource<Int, Order> =
orderDAO.getOrdersByUser(id)
suspend fun deleteAll() = orderDAO.deleteAll()
override suspend fun getById(id: Int): Order = orderDAO.getById(id)
suspend fun insertOrders(orders: List<Order>) =
orderDAO.insert(*orders.toTypedArray())
}

View File

@ -0,0 +1,17 @@
package com.example.flowershopapp.Entities.Repository.Order
import androidx.paging.PagingData
import com.example.flowershopapp.Entities.Model.Bouquet
import com.example.flowershopapp.Entities.Model.Order
import com.example.flowershopapp.Entities.Model.OrderByDate
import kotlinx.coroutines.flow.Flow
interface OrderRepository {
suspend fun getBouquetsByOrder(orderId: Int): Flow<List<Bouquet>>
fun getOrdersByUser(id: Int): Flow<PagingData<Order>>
suspend fun insert(order: Order)
suspend fun insertWithReturn(order: Order): Order
suspend fun getOrdersByDate(userId: Int, starDate: String, endDate: String): Flow<OrderByDate>
suspend fun delete(order: Order)
suspend fun getById(id: Int): Order
}

View File

@ -0,0 +1,15 @@
package com.example.flowershopapp.Entities.Repository.OrderBouquets
import com.example.flowershopapp.Entities.DAO.OrdersWithBouquet
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
class OfflineOrdersWithBouquetsRepository(private val orderBouquetsDAO: OrdersWithBouquet) :
OrdersWithBouquetsRepository {
override suspend fun getAll() = orderBouquetsDAO.getAll()
override suspend fun insert(order: OrderBouquetCrossRef) = orderBouquetsDAO.insert(order)
override suspend fun delete(order: OrderBouquetCrossRef) = orderBouquetsDAO.delete(order)
override suspend fun deleteAll() = orderBouquetsDAO.deleteAll()
suspend fun insertAll(orderBouquet: List<OrderBouquetCrossRef>) =
orderBouquetsDAO.insert(*orderBouquet.toTypedArray())
}

View File

@ -0,0 +1,10 @@
package com.example.flowershopapp.Entities.Repository.OrderBouquets
import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef
interface OrdersWithBouquetsRepository {
suspend fun getAll(): List<OrderBouquetCrossRef>
suspend fun insert(order: OrderBouquetCrossRef)
suspend fun delete(order: OrderBouquetCrossRef)
suspend fun deleteAll()
}

View File

@ -0,0 +1,15 @@
package com.example.flowershopapp.Entities.Repository.User
import com.example.flowershopapp.Entities.DAO.UserDAO
import com.example.flowershopapp.Entities.Model.User
class OfflineUserRepository(private val userDAO: UserDAO) : UserRepository {
override suspend fun getAll(): List<User> = userDAO.getAll()
override suspend fun getUserByName(userName: String): User = userDAO.getUserByName(userName)
override suspend fun insert(user: User) = userDAO.insert(user)
override suspend fun update(user: User) = userDAO.update(user)
override suspend fun delete(user: User) = userDAO.delete(user)
override suspend fun deleteAll() = userDAO.deleteAll()
suspend fun insertUsers(users: List<User>) =
userDAO.insert(*users.toTypedArray())
}

View File

@ -0,0 +1,12 @@
package com.example.flowershopapp.Entities.Repository.User
import com.example.flowershopapp.Entities.Model.User
interface UserRepository {
suspend fun getAll(): List<User>
suspend fun getUserByName(userName: String): User
suspend fun insert(user: User)
suspend fun update(user: User)
suspend fun delete(user: User)
suspend fun deleteAll()
}

View File

@ -0,0 +1,26 @@
package com.example.flowershopapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.flowershopapp.ComposeUI.Navigation.MainNavbar
import com.example.flowershopapp.ui.theme.FlowerShopAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
FlowerShopAppTheme {
Surface(
modifier = Modifier
.fillMaxSize()
) {
MainNavbar()
}
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.example.flowershopapp.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)
val Color1 = Color(0xFF2e3350)

View File

@ -0,0 +1,66 @@
package com.example.flowershopapp.ui.theme
import android.app.Activity
import android.os.Build
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 = Color1,
secondary = PurpleGrey40,
tertiary = Pink40,
/*
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F)
*/
)
@Composable
fun FlowerShopAppTheme(
darkTheme: Boolean = false,
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.example.flowershopapp.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
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
)
*/
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M349.6,64c-36.4,0 -70.72,16.74 -93.6,43.95C233.12,80.74 198.8,64 162.4,64 97.92,64 48,114.22 48,179.1c0,79.52 70.72,143.35 177.84,241.69L256,448l30.16,-27.21C393.28,322.44 464,258.61 464,179.1 464,114.22 414.08,64 349.6,64zM268.84,393.26l-4.22,3.87 -8.62,7.77 -8.62,-7.77 -4.21,-3.87c-50.42,-46.28 -93.96,-86.25 -122.75,-121.99C92.47,236.55 80,208.13 80,179.1c0,-22.86 8.42,-43.93 23.72,-59.32C118.96,104.44 139.8,96 162.4,96c26.13,0 51.97,12.17 69.11,32.54L256,157.66l24.49,-29.12C297.63,108.17 323.46,96 349.6,96c22.6,0 43.44,8.44 58.69,23.78C423.58,135.16 432,156.23 432,179.1c0,29.03 -12.47,57.46 -40.42,92.17 -28.78,35.74 -72.32,75.71 -122.74,121.99z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M5.301,3.002c-0.889,-0.047 -1.759,0.247 -2.404,0.893 -1.29,1.292 -1.175,3.49 0.26,4.926l0.515,0.515L8.332,14l4.659,-4.664 0.515,-0.515c1.435,-1.437 1.55,-3.634 0.26,-4.926 -1.29,-1.292 -3.483,-1.175 -4.918,0.262l-0.516,0.517 -0.517,-0.517C7.098,3.438 6.19,3.049 5.3,3.002z"
android:fillColor="#f05542"/>
</vector>

View File

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

View File

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

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22Z"
android:strokeAlpha="0.4"
android:fillColor="#292D32"
android:fillAlpha="0.4"/>
<path
android:pathData="M16,11.25H12.75V8C12.75,7.59 12.41,7.25 12,7.25C11.59,7.25 11.25,7.59 11.25,8V11.25H8C7.59,11.25 7.25,11.59 7.25,12C7.25,12.41 7.59,12.75 8,12.75H11.25V16C11.25,16.41 11.59,16.75 12,16.75C12.41,16.75 12.75,16.41 12.75,16V12.75H16C16.41,12.75 16.75,12.41 16.75,12C16.75,11.59 16.41,11.25 16,11.25Z"
android:fillColor="#292D32"/>
</vector>

View File

View File

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22Z"
android:strokeAlpha="0.4"
android:fillColor="#292D32"
android:fillAlpha="0.4"/>
<path
android:pathData="M15.92,12.75H7.92C7.51,12.75 7.17,12.41 7.17,12C7.17,11.59 7.51,11.25 7.92,11.25H15.92C16.33,11.25 16.67,11.59 16.67,12C16.67,12.41 16.34,12.75 15.92,12.75Z"
android:fillColor="#292D32"/>
</vector>

View File

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M1,1C0.448,1 0,1.448 0,2C0,2.552 0.448,3 1,3H3.206L5.985,14.909C6.399,16.682 7.955,17.946 9.765,17.998V18H17.587C19.536,18 21.201,16.596 21.53,14.675L22.786,7.337C22.995,6.116 22.054,5 20.814,5H5.727L4.974,1.773C4.868,1.32 4.465,1 4,1H1ZM6.194,7L7.933,14.455C8.144,15.36 8.951,16 9.881,16H17.587C18.562,16 19.394,15.298 19.559,14.337L20.814,7H6.194Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M8,23C9.105,23 10,22.105 10,21C10,19.895 9.105,19 8,19C6.895,19 6,19.895 6,21C6,22.105 6.895,23 8,23Z"
android:fillColor="#000000"/>
<path
android:pathData="M19,23C20.105,23 21,22.105 21,21C21,19.895 20.105,19 19,19C17.895,19 17,19.895 17,21C17,22.105 17.895,23 19,23Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M365.71,3.47c-42.49,0 -82.14,18.18 -109.71,49.5C228.43,21.65 188.77,3.47 146.29,3.47C65.62,3.47 0,69.09 0,149.76c0,33.75 11.78,66.69 33.16,92.74L256,508.53l222.74,-265.91l0.1,-0.12C500.22,216.45 512,183.51 512,149.76C512,69.09 446.38,3.47 365.71,3.47zM454.66,222.55L256,459.71L57.34,222.55c-16.76,-20.45 -25.99,-46.3 -25.99,-72.8c0,-63.38 51.56,-114.94 114.94,-114.94c39.15,0 75.25,19.65 96.56,52.57L256,107.71l13.16,-20.32c21.31,-32.92 57.41,-52.57 96.56,-52.57c63.38,0 114.94,51.56 114.94,114.94C480.65,176.25 471.42,202.1 454.66,222.55z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="34"
android:viewportHeight="34">
<path
android:pathData="M2.503,16.822C1.397,16.822 0.5,15.967 0.5,14.913C0.5,14.387 0.728,13.884 1.13,13.523L15.627,0.519C16.399,-0.173 17.601,-0.173 18.373,0.519L32.87,13.523C33.675,14.246 33.714,15.454 32.956,16.221C32.577,16.605 32.05,16.822 31.497,16.822L31.018,16.822L31.018,30.183C31.018,32.291 29.225,34 27.013,34L23.004,34C21.347,34 20.004,32.657 20.004,31L20.004,24.457L20.004,24.457C20.004,23.444 19.174,22.609 18.149,22.552L18.001,22.548L15.999,22.548C14.937,22.548 14.061,23.339 14.001,24.316L13.996,24.457L13.996,31C13.996,32.657 12.653,34 10.996,34L6.987,34L6.987,34C4.775,34 2.982,32.291 2.982,30.183L2.982,16.822L2.503,16.822ZM23.607,32.091L27.013,32.091L27.013,32.091C28.119,32.091 29.016,31.237 29.016,30.183L29.016,15.113C29.016,15.003 29.105,14.913 29.216,14.913L30.975,14.913C31.085,14.913 31.175,14.824 31.175,14.713C31.175,14.656 31.151,14.602 31.108,14.564L17.668,2.508C17.288,2.167 16.712,2.167 16.332,2.508L2.892,14.564C2.809,14.638 2.802,14.765 2.876,14.847C2.914,14.889 2.968,14.913 3.025,14.913L4.784,14.913C4.895,14.913 4.984,15.003 4.984,15.113L4.984,30.183L4.984,30.183C4.984,31.237 5.881,32.091 6.987,32.091L10.393,32.091C11.277,32.091 11.993,31.375 11.994,30.491L11.994,24.429L11.994,24.429L12,24.233C12.123,22.22 13.874,20.639 15.999,20.639L18.03,20.64L18.236,20.646C20.348,20.762 22.007,22.432 22.007,24.457L22.007,30.491C22.007,31.375 22.723,32.091 23.607,32.091Z"
android:strokeWidth="1"
android:fillColor="#252528"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2ZM8.5,8.5C8.5,6.567 10.067,5 12,5C13.933,5 15.5,6.567 15.5,8.5C15.5,10.433 13.933,12 12,12C10.067,12 8.5,10.433 8.5,8.5ZM5.278,16.34C6.927,14.992 9.335,14 12,14C14.665,14 17.073,14.992 18.722,16.34C17.297,18.542 14.819,20 12,20C9.181,20 6.703,18.542 5.278,16.34Z"
android:strokeWidth="1"
android:fillColor="#09244B"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:fillColor="#FF000000"
android:pathData="M16,16A7,7 0,1 0,9 9,7 7,0 0,0 16,16ZM16,4a5,5 0,1 1,-5 5A5,5 0,0 1,16 4Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M17,18H15A11,11 0,0 0,4 29a1,1 0,0 0,1 1H27a1,1 0,0 0,1 -1A11,11 0,0 0,17 18ZM6.06,28A9,9 0,0 1,15 20h2a9,9 0,0 1,8.94 8Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M22,11h-4.17l3.24,-3.24 -1.41,-1.42L15,11h-2V9l4.66,-4.66 -1.42,-1.41L13,6.17V2h-2v4.17L7.76,2.93 6.34,4.34 11,9v2H9L4.34,6.34 2.93,7.76 6.17,11H2v2h4.17l-3.24,3.24 1.41,1.42L9,13h2v2l-4.66,4.66 1.42,1.41L11,17.83V22h2v-4.17l3.24,3.24 1.42,-1.41L13,15v-2h2l4.66,4.66 1.41,-1.42L17.83,13H22z"/>
</vector>

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