7 Commits

Author SHA1 Message Date
778d5680dc Загрузил(а) файлы в 'reports' 2023-12-29 19:05:51 +04:00
0996e16a84 feature: lab4 maybe done 2023-12-09 20:14:52 +04:00
f1f3dcf01f feature&fix: lab4 add viewmodel, fix repos 2023-11-25 16:25:39 +04:00
ff959a86aa feature: lab4 add repos 2023-11-25 14:41:44 +04:00
d88daf5fd7 feature: lab3 is done 2023-11-14 11:52:44 +04:00
471645fb6e feature: UI, lab2 maybe done 2023-10-30 18:46:41 +04:00
4902705f9c empty activity test 2023-10-08 13:05:13 +04:00
59 changed files with 3014 additions and 45 deletions

1
.idea/gradle.xml generated
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>

327
.idea/workspace.xml generated
View File

@@ -1,11 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;last_opened_file_path&quot;: &quot;C:/Users/Danil/Desktop/5 семестр/ПМУ/labs&quot;
<component name="AndroidLayouts">
<shared>
<config />
</shared>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7c94e195-a540-483e-9a1c-11797aeb1741" name="Changes" comment="feature&amp;fix: lab4 add viewmodel, fix repos">
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/AppContainer.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/AppDataContainer.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/MainActivity.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/AppViewModelProvider.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/app/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/AndroidManifest.xml" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/AndroidManifest.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/App.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/App.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/Basket.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/Basket.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/BasketItemUI.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/BasketItemUI.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/AddService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/AddService.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/ListOfServices.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/ListOfServices.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/Service.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/Service.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavController.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavController.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavItem.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavItem.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/Orders.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/Orders.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Login.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Login.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Profile.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Profile.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ProfileChange.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ProfileChange.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ProfileNotAuth.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Registration.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Registration.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/UIComponents/MyTextField.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/UIComponents/MyTextField.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/BasketDao.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/BasketDao.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/OrderDao.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/OrderDao.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/ServiceDao.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/ServiceDao.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/UserDao.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/dao/UserDao.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/AppDatabase.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/AppDatabase.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Basket.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Basket.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/BasketItem.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/BasketService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/BasketService.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/OrderService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/OrderService.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Service.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Service.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/User.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/User.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/repository/BasketRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/repository/BasketRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/repository/OrderRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/repository/OrderRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/repository/ServiceRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/repository/ServiceRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/BasketViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/BasketViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/OrderViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/OrderViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/ServiceViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/ServiceViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/UserViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/UserViewModel.kt" 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="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[adb-6681ed71-4kOMN8._adb-tls-connect._tcp]" />
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$">
<ProjectState />
</projectState>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="resourceFile" />
<option value="Class" />
<option value="Kotlin Class" />
</list>
</option>
</component>
<component name="GenerateSignedApkSettings">
<option name="KEY_STORE_PATH" value="C:\Users\Danil\Desktop\app.jks" />
<option name="KEY_ALIAS" value="key0" />
<option name="BUILD_TARGET_KEY" value="apk" />
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="LabWork03" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="KotlinCodeInsightWorkspaceSettings">
<option name="optimizeImportsOnTheFly" value="true" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2W7FGorxnfmDBVh7paZL1FPxAKQ" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ApkExportedModule": "My_Application.app",
"ExportApk.ApkPathForMy_Application.app": "C:\\Users\\Danil\\Desktop\\MDP\\labs\\app",
"PROJECT_TRUSTED_KEY": "true",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"cidr.known.project.marker": "true",
"com.android.tools.idea.devicemanager.tab": "Physical",
"last_opened_file_path": "C:/Users/Danil/Downloads/test",
"project.structure.last.edited": "Modules",
"project.structure.proportion": "0.17",
"project.structure.side.proportion": "0.2",
"settings.editor.selected.configurable": "emulator"
},
"keyToStringList": {
"ExportApk.BuildVariants": [
"release"
],
"com.android.tools.idea.sqlite.queryhistory": [
"insert into tbl_user values (1, \"Danil\", \"Markov\", \"danil@mail.ru\", \"123\", \"ADMIN\", null)",
"delete from tbl_user where userId = 6",
"delete from tbl_user where userId = 4",
"delete from tbl_user where userId = 2"
]
}
}</component>
<component name="RunManager">
}]]></component>
<component name="PsdUISettings">
<option name="MODULE_TAB" value="Signing Configs" />
<option name="LAST_EDITED_SIGNING_CONFIG" value="debug" />
<option name="LAST_EDITED_BUILD_TYPE" value="release" />
</component>
<component name="RecentsManager">
<key name="android.template.-1413981578">
<recent name="com.example.myapplication.Navbar" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\Danil\Desktop\MDP\labs\app\src\main\res\drawable" />
<recent name="C:\Users\Danil\Desktop\MDP\labs\app\src\main\java\com\example\myapplication\UIComponents" />
</key>
</component>
<component name="RunManager" selected="Android App.app">
<configuration name="App" type="AndroidRunConfigurationType" factoryName="Android App" temporary="true">
<module name="My_Application.app.main" />
<option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
<option name="CLEAR_APP_STORAGE" value="false" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="specific_activity" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="com.example.myapplication.App" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="app" type="AndroidRunConfigurationType" factoryName="Android App">
<module name="My_Application.app.main" />
<option name="DEPLOY" value="true" />
@@ -72,5 +270,124 @@
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="build.gradle.kts" type="KotlinStandaloneScriptRunConfigurationType" temporary="true" nameIsGenerated="true">
<module name="My_Application" />
<option name="filePath" value="$PROJECT_DIR$/app/build.gradle.kts" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Kotlin script (Beta).build.gradle.kts" />
<item itemvalue="Android App.App" />
</list>
</recent_temporary>
</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="7c94e195-a540-483e-9a1c-11797aeb1741" name="Changes" comment="" />
<created>1696075618936</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1696075618936</updated>
</task>
<task id="LOCAL-00001" summary="empty activity test">
<created>1696075970008</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1696075970008</updated>
</task>
<task id="LOCAL-00002" summary="empty activity test">
<created>1696755925297</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1696755925297</updated>
</task>
<task id="LOCAL-00003" summary="feature: UI, lab2 maybe done">
<created>1698677204522</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1698677204522</updated>
</task>
<task id="LOCAL-00004" summary="feature: lab3 is done">
<created>1699943645305</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1699943645305</updated>
</task>
<task id="LOCAL-00005" summary="feature: lab3 is done">
<created>1699948377847</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1699948377847</updated>
</task>
<task id="LOCAL-00006" summary="feature: lab4 add repos">
<created>1700908930838</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1700908930838</updated>
</task>
<task id="LOCAL-00007" summary="feature&amp;fix: lab4 add viewmodel, fix repos">
<created>1700915139985</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1700915139985</updated>
</task>
<option name="localTasksCounter" value="8" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="empty activity test" />
<MESSAGE value="feature: UI, lab2 maybe done" />
<MESSAGE value="feature: lab3 is done" />
<MESSAGE value="feature: lab4 add repos" />
<MESSAGE value="feature&amp;fix: lab4 add viewmodel, fix repos" />
<option name="LAST_COMMIT_MESSAGE" value="feature&amp;fix: lab4 add viewmodel, fix repos" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/OrderViewModel.kt</url>
<line>38</line>
<option name="timeStamp" value="18" />
</line-breakpoint>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/OrderViewModel.kt</url>
<line>36</line>
<option name="timeStamp" value="19" />
</line-breakpoint>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavController.kt</url>
<line>63</line>
<option name="timeStamp" value="23" />
</line-breakpoint>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/viewmodel/ServiceViewModel.kt</url>
<line>30</line>
<option name="timeStamp" value="25" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>

View File

@@ -1,16 +1,17 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id ("kotlin-kapt")
}
android {
namespace = "com.example.myapplication"
compileSdk = 33
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 29
targetSdk = 33
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
@@ -30,11 +31,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "17"
}
buildFeatures {
compose = true
@@ -49,16 +50,22 @@ android {
}
}
dependencies {
kotlin {
jvmToolchain(17)
}
implementation("androidx.core:core-ktx:1.9.0")
apply(plugin = "kotlin-kapt")
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.2")
implementation("androidx.activity:activity-compose:1.8.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material")
implementation("androidx.navigation:navigation-runtime-ktx:2.7.5")
implementation("androidx.compose.material3:material3:1.1.2")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
@@ -66,4 +73,21 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation ("androidx.activity:activity-ktx:1.8.0")
implementation ("androidx.fragment:fragment-ktx:1.6.2")
implementation ("io.coil-kt:coil-compose:1.4.0")
implementation ("com.google.code.gson:gson:2.9.0")
implementation("androidx.navigation:navigation-compose:2.7.5")
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0-beta01")
implementation ("androidx.paging:paging-compose:3.2.1")
implementation ("androidx.paging:paging-runtime:3.2.1")
//ROOM
val room_version = "2.5.2"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
kapt("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
implementation("androidx.room:room-paging:$room_version")
}

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"

View File

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

View File

@@ -0,0 +1,13 @@
package com.example.myapplication
import com.example.myapplication.repository.BasketRepository
import com.example.myapplication.repository.OrderRepository
import com.example.myapplication.repository.ServiceRepository
import com.example.myapplication.repository.UserRepository
interface AppContainer {
val serviceRepo: ServiceRepository
val userRepo: UserRepository
val orderRepo: OrderRepository
val basketRepo: BasketRepository
}

View File

@@ -0,0 +1,15 @@
package com.example.myapplication
import android.content.Context
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.repository.BasketRepository
import com.example.myapplication.repository.OrderRepository
import com.example.myapplication.repository.ServiceRepository
import com.example.myapplication.repository.UserRepository
class AppDataContainer(context: Context) : AppContainer {
override val serviceRepo = ServiceRepository(AppDatabase.getInstance(context).serviceDao())
override val userRepo = UserRepository(AppDatabase.getInstance(context).userDao())
override val orderRepo = OrderRepository(AppDatabase.getInstance(context).orderDao())
override val basketRepo = BasketRepository(AppDatabase.getInstance(context).basketDao())
}

View File

@@ -3,44 +3,51 @@ package com.example.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplication.ui.theme.MyApplicationTheme
import com.example.myapplication.composeui.Navbar.NavBar
import com.example.myapplication.model.User
import com.example.myapplication.ui.theme.AppTheme
import com.example.myapplication.ui.theme.BlueMain
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
AppTheme (darkTheme = false){
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
modifier = Modifier
.fillMaxSize()
.background(BlueMain),
) {
Greeting("Android")
NavBar();
}
}
}
}
}
class GlobalUser private constructor() {
private var user: User? = null
fun setUser(user: User?) {
this.user = user
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
fun getUser(): User? {
return user
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MyApplicationTheme {
Greeting("Android")
companion object {
private var instance: GlobalUser? = null
fun getInstance(): GlobalUser {
return instance ?: synchronized(this) {
instance ?: GlobalUser().also { instance = it }
}
}
}
}

View File

@@ -0,0 +1,49 @@
package com.example.myapplication
import android.os.Parcel
import android.os.Parcelable
data class TestServiceItem(
val image: Int,
val name: String?,
val animals: ArrayList<String>?,
val price: Double
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.createStringArrayList(),
parcel.readDouble()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(image)
parcel.writeString(name)
parcel.writeStringList(animals)
parcel.writeDouble(price)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TestServiceItem> {
override fun createFromParcel(parcel: Parcel): TestServiceItem {
return TestServiceItem(parcel)
}
override fun newArray(size: Int): Array<TestServiceItem?> {
return arrayOfNulls(size)
}
}
}
fun getServices(): List<TestServiceItem>{
return listOf(
)
}

View File

@@ -0,0 +1,108 @@
package com.example.myapplication.composeui.Basket
import androidx.compose.foundation.background
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.myapplication.GlobalUser
import com.example.myapplication.composeui.Profile.Login
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.BasketViewModel
import com.example.myapplication.viewmodel.OrderViewModel
@Composable
fun Basket(navController : NavHostController, basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory), orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val user = GlobalUser.getInstance().getUser()
if (user == null){
Login(navController = navController)
}else{
basketViewModel.updateSubTotal(user.userId!!)
val total = basketViewModel.total.value
val basketList by basketViewModel.getBasketServices(user.userId).collectAsState(initial = null)
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val serviceList: List<Service>? = basketList?.services
if (serviceList != null){
orderViewModel.updateSelectedItems(serviceList)
for (item in serviceList){
BasketItemUI(item = item)
}
}
Box(modifier = Modifier
.clip(RoundedCornerShape(15.dp, 15.dp, 0.dp, 0.dp))
.background(Color.Transparent)
.height(130.dp),
){
Column (modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.background(Color.White)
.padding(PaddingValues(15.dp)),
){
Row (
horizontalArrangement = Arrangement.SpaceBetween,
){
Text(
text = "Total: ",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(1f)
)
Text(
text = "$$total",
style = MaterialTheme.typography.bodyMedium
)
}
}
Button(
onClick = {
orderViewModel.createOrder()
},
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.align(Alignment.BottomCenter),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Confirm order", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
}
}
}
}

View File

@@ -0,0 +1,162 @@
package com.example.myapplication.composeui.Basket
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.GlobalUser
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextPrimary
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.BasketViewModel
import com.example.myapplication.viewmodel.OrderViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun BasketItemUI(item: Service, basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory), orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val userId = GlobalUser.getInstance().getUser()?.userId!!
val basketStateId = remember { mutableIntStateOf(0) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
basketStateId.value = basketViewModel.getUsersBasket(userId).basketId!!
}
}
val quantityState by basketViewModel.getQuantityState(basketStateId.value, item.serviceId!!).collectAsState()
Box(
modifier = Modifier
.padding(0.dp, 0.dp, 0.dp, 10.dp)
.height(150.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(15.dp),
clip = false
),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
item.photo?.let { painterResource(id = it) }?.let {
Image(
painter = it,
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.Top,
) {
Text(
text = item.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium
)
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "$${item.price}",
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "$quantityState",
color = TextPrimary
)
Column(verticalArrangement = Arrangement.SpaceAround) {
Button(
onClick = {
basketViewModel.incrementQuantity(basketStateId.intValue, item.serviceId!!)
},
modifier = Modifier
.size(42.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "+")
}
Button(
onClick = {
if (quantityState == 1) basketViewModel.deleteServiceFromBasket(basketStateId.intValue,
item.serviceId!!
)
basketViewModel.decrementQuantity(basketStateId.intValue, item.serviceId!!)
},
modifier = Modifier
.size(42.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "-")
}
}
}
}
}
}
}

View File

@@ -0,0 +1,214 @@
package com.example.myapplication.composeui.List_of_Services
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextPrimary
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.ServiceViewModel
import kotlinx.coroutines.Dispatchers
@Composable
fun AddService (navController: NavController, service: Service, serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val create = service.serviceId == null
LaunchedEffect(Dispatchers.Default){
if(!create){
serviceViewModel.service.value.serviceId = service.serviceId
serviceViewModel.service.value.photo = service.photo
}
}
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Box(
modifier = Modifier
.padding(0.dp, 0.dp, 0.dp, 10.dp)
.height(150.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(15.dp),
clip = false
),
){
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
){
if(create){
Image(
painter = painterResource(id = serviceViewModel.photo.intValue),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
}else{
service.photo?.let { painterResource(id = it) }?.let {
Image(
painter = it,
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
}
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.Top,
){
if(create){
Text(
text = serviceViewModel.name.value,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
}else{
Text(
text = service.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
}
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
){
if(create){
Text(
text = serviceViewModel.price.doubleValue.toString(),
style = MaterialTheme.typography.bodyMedium,
)
}else{
Text(
text = service.price.toString(),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
if(create){
MyTextField(label = "Service name"){
serviceViewModel.name.value = it
}
}else{
MyTextField(label = service.name){
serviceViewModel.service.value.name = it
}
}
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
if(create){
MyTextField(label = "Price"){
serviceViewModel.price.doubleValue = it.toDouble()
}
}else{
MyTextField(label = service.price.toString()){
serviceViewModel.service.value.price = it.toDouble()
}
}
}
}
Button(
onClick = {
// val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
// launcher.launch(intent)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Upload image", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
Button(
onClick = {
if (create)
serviceViewModel.createService()
else
serviceViewModel.service.let { serviceViewModel.updateService() }
navController.navigate(NavItem.ListOfServices.route)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
if(create) {
Text(text = "Add service", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}else{
Text(text = "Update service", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
}
}
}

View File

@@ -0,0 +1,49 @@
package com.example.myapplication.composeui.List_of_Services
import SearchBar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.ServiceViewModel
@Composable
fun ListOfServices(navController: NavHostController, serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)){
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(bottom = 60.dp)
){
SearchBar(
modifier = Modifier)
{
searchText ->
//TODO search logic
}
val services = serviceViewModel.serviceList.collectAsLazyPagingItems()
LazyColumn(modifier = Modifier.padding(15.dp, 0.dp)){
items(
count = services.itemCount,
key = services.itemKey { service -> service.serviceId!! }
){
index: Int ->
val service: Service? = services[index]
if (service != null){
Service(navController, item = service)
}
}
}
}
}

View File

@@ -0,0 +1,171 @@
package com.example.myapplication.composeui.List_of_Services
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.myapplication.GlobalUser
import com.example.myapplication.model.BasketService
import com.example.myapplication.model.RoleEnum
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextPrimary
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.BasketViewModel
import com.example.myapplication.viewmodel.ServiceViewModel
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@Composable
fun Service(navController: NavHostController, item: Service, basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory), serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val user = GlobalUser.getInstance().getUser()
Box(
modifier = Modifier
.padding(0.dp, 0.dp, 0.dp, 10.dp)
.height(150.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(15.dp),
clip = false
),
){
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
){
item.photo?.let { painterResource(id = it) }?.let {
Image(
painter = it,
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.Top,
){
Text(
text = item.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
if(user?.role == RoleEnum.Admin){
Button(
onClick = {
runBlocking {
launch(Dispatchers.Default){
serviceViewModel.deleteService(item)
}
}
},
modifier = Modifier
.size(42.dp)
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
){
Icon(imageVector = Icons.Default.Delete, contentDescription = null)
}
}
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
){
Text(
text = "$${item.price}",
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium,
)
Button(
onClick = {
runBlocking{
launch(Dispatchers.Default){
basketViewModel.addToBasket(BasketService(basketViewModel.getUsersBasket(user?.userId!!).basketId!!, item.serviceId!!, 1))
}
}
},
modifier = Modifier
.size(42.dp)
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
){
Text(text = "+")
}
if(user?.role == RoleEnum.Admin){
Button(
onClick = {
serviceViewModel.service.value = item
navController.navigate("add_service/${Gson().toJson(item)}")
},
modifier = Modifier
.size(42.dp)
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
){
Icon(imageVector = Icons.Default.Create, contentDescription = null)
}
}
}
}
}
}

View File

@@ -0,0 +1,115 @@
package com.example.myapplication.composeui.Navbar
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ControlledComposition
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat.getColor
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.R
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.BlueNavbar
import com.example.myapplication.ui.theme.GreenBtn
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun NavBar(){
val navController = rememberNavController()
val items = listOf(
NavItem.ListOfServices,
NavItem.Basket,
NavItem.Profile
)
Scaffold(bottomBar = {
BottomNavigation(
backgroundColor = BlueNavbar,
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.clip(RoundedCornerShape(15.dp, 15.dp, 0.dp, 0.dp)),
){
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry
items.forEach { screen ->
val isSelected = currentDestination?.destination?.route == screen.route
BottomNavigationItem(
selected = isSelected,
icon = {
Icon(painterResource(screen.icon),
null,
modifier = Modifier,
GreenBtn)
},
modifier = Modifier
.padding(15.dp),
onClick = {
navController.navigate(screen.route){
if (!isSelected) {
navController.graph.startDestinationRoute?.let {
navController.popBackStack(it, inclusive = true)
}
navController.navigate(screen.route) {
launchSingleTop
}
}
navController.navigate(screen.route)
}
}
)
}
}
}){paddingValues ->
Column(modifier = Modifier
.fillMaxSize()
.padding(bottom = paddingValues.calculateBottomPadding())
.verticalScroll(rememberScrollState())) {
}
NavController(navController = navController)
}
}
@Composable
@Preview
fun NavigatePreview(){
NavBar()
}

View File

@@ -0,0 +1,70 @@
package com.example.myapplication.composeui.Navbar
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.example.myapplication.composeui.Basket.Basket
import com.example.myapplication.composeui.List_of_Services.AddService
import com.example.myapplication.composeui.List_of_Services.ListOfServices
import com.example.myapplication.composeui.Orders.Orders
import com.example.myapplication.composeui.Profile.Login
import com.example.myapplication.composeui.Profile.Profile
import com.example.myapplication.composeui.Profile.ProfileChange
import com.example.myapplication.composeui.Profile.Registration
import com.example.myapplication.model.Service
import com.google.gson.Gson
@Composable
fun NavController(navController : NavHostController){
NavHost(
navController = navController,
startDestination = NavItem.ListOfServices.route,
){
composable(
NavItem.ListOfServices.route
){
ListOfServices(navController)
}
composable(
NavItem.Basket.route
){
Basket(navController)
}
composable(
NavItem.Profile.route
){
Profile(navController)
}
composable(
NavItem.ProfileChange.route
){
ProfileChange(navController)
}
composable(
NavItem.Orders.route
){
Orders(navController)
}
composable(
NavItem.Login.route
){
Login(navController)
}
composable(
NavItem.Registration.route
){
Registration(navController)
}
composable(
NavItem.AddService.route
){
backStackEntry ->
val serviceItemString = backStackEntry.arguments?.getString("serviceItem")
val serviceItem = Gson().fromJson(serviceItemString, Service::class.java)
serviceItem?.let { AddService(navController, it)
}
}
}
}

View File

@@ -0,0 +1,42 @@
package com.example.myapplication.composeui.Navbar
import androidx.annotation.DrawableRes
import com.example.myapplication.R
sealed class NavItem(
val route: String,
@DrawableRes val icon: Int
){
object ListOfServices : NavItem(
"list_of_services",
R.drawable.icon_list_of_services
)
object Basket : NavItem(
"basket",
R.drawable.icon_basket
)
object Profile : NavItem(
"profile",
R.drawable.icon_profile
)
object ProfileChange : NavItem(
"profile_change",
R.drawable.icon_profile
)
object Orders : NavItem(
"orders",
R.drawable.icon_profile
)
object Login : NavItem(
"login",
R.drawable.icon_profile
)
object Registration : NavItem(
"registration",
R.drawable.icon_profile
)
object AddService : NavItem(
"add_service/{serviceItem}",
R.drawable.icon_list_of_services
)
}

View File

@@ -0,0 +1,49 @@
package com.example.myapplication.composeui.Orders
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.myapplication.model.Order
import java.text.SimpleDateFormat
import java.util.Date
@Composable
fun OrderItem (order: Order){
val dateFormat = SimpleDateFormat("dd-MM-yyyy")
Column (
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(15.dp))
){
Row (
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.background(Color.White)
.fillMaxWidth()
.padding(horizontal = 15.dp, vertical = 10.dp),
){
Text(text = "Order:${order.orderId}", modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
Text(text = "${order.total}", style = MaterialTheme.typography.bodyMedium)
}
Row (
modifier = Modifier
.background(Color.White)
.fillMaxWidth()
.padding(horizontal = 15.dp),
){
Text(text = dateFormat.format(Date(order.date)), style = MaterialTheme.typography.bodyMedium)
}
}
}

View File

@@ -0,0 +1,49 @@
package com.example.myapplication.composeui.Orders
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.GlobalUser
import com.example.myapplication.model.Order
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.OrderViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun Orders (navController: NavController, orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val ordersList = remember { mutableStateListOf<Order>() }
val user = GlobalUser.getInstance().getUser()
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
orderViewModel.getUserOrders(user?.userId!!).collect { data ->
ordersList.clear()
ordersList.addAll(data.orders)
}
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
itemsIndexed(ordersList){_, item ->
OrderItem(item)
}
}
}

View File

@@ -0,0 +1,140 @@
package com.example.myapplication.composeui.Profile
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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
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.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.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextSecondary
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.UserViewModel
@Composable
fun Login (navController: NavController, userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em))
)
}
Column (
){
var isEmailValid by remember { mutableStateOf(true)}
var isPasswordValid by remember { mutableStateOf(true)}
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Password", onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
userViewModel.authUser()
navController.navigate(NavItem.Profile.route)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Login",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White)
)
}
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "Don't have an account? ",
style = MaterialTheme.typography.bodyMedium
.copy(TextSecondary)
)
Text(
text = "Sign up",
style = MaterialTheme.typography.bodyMedium
.copy(GreenBtn),
modifier = Modifier
.clickable { navController.navigate(NavItem.Registration.route) }
)
}
}
}

View File

@@ -0,0 +1,165 @@
package com.example.myapplication.composeui.Profile
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.example.myapplication.GlobalUser
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.model.RoleEnum
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.RedBtn
@Composable
fun Profile(navController: NavHostController){
val user = GlobalUser.getInstance().getUser()
if(user == null){
Login(navController = navController)
}else{
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Box(modifier = Modifier
.clip(CircleShape)
.size(200.dp)
.background(Color.White)
){
// TODO: upload profile image
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = user.name + " " + user.surname,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = user.email,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Button(
onClick = { navController.navigate(NavItem.ProfileChange.route) },
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Change profile",
style = MaterialTheme.typography.bodyMedium,
color = Color.White
)
}
Button(
onClick = { navController.navigate(NavItem.Orders.route) },
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Orders",
style = MaterialTheme.typography.bodyMedium,
color = Color.White
)
}
if(user.role == RoleEnum.Admin){
Button(
onClick = { navController.navigate("add_service/{}") },
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Add service",
style = MaterialTheme.typography.bodyMedium,
color = Color.White
)
}
}
Button(
onClick = {
GlobalUser.getInstance().setUser(null)
navController.navigate(NavItem.Login.route)
},
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = RedBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Icon(
Icons.Default.ExitToApp,
contentDescription = null,
modifier = Modifier,
)
Text(
text = "Exit",
style = MaterialTheme.typography.bodyMedium,
color = Color.White
)
}
}
}
}

View File

@@ -0,0 +1,147 @@
package com.example.myapplication.composeui.Profile
import androidx.compose.foundation.background
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.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.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
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.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.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.myapplication.GlobalUser
import com.example.myapplication.R
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.UserViewModel
@Composable
fun ProfileChange (navController: NavHostController, userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val user = GlobalUser.getInstance().getUser()
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Box(modifier = Modifier
.clip(CircleShape)
.size(200.dp)
.background(Color.White)
.padding(PaddingValues(0.dp))
){
Icon(
painterResource(id = R.drawable.upload),
contentDescription = null,
modifier = Modifier.align(Alignment.Center),
GreenBtn
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = "${user?.name} ${user?.surname}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = "${user?.email}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Name", onValueChanged = {userViewModel.name.value = it})
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Surname", onValueChanged = {userViewModel.surname.value = it})
}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Password", onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
userViewModel.updateUser()
navController.navigate(NavItem.Profile.route)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Confirm changes", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
}
}

View File

@@ -0,0 +1,144 @@
package com.example.myapplication.composeui.Profile
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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
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.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.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextSecondary
import com.example.myapplication.viewmodel.AppViewModelProvider
import com.example.myapplication.viewmodel.UserViewModel
@Composable
fun Registration (navController: NavController, userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp),
horizontalArrangement = Arrangement.Center,
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em))
)
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Name", onValueChanged = { userViewModel.name.value = it })
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Surname", onValueChanged = { userViewModel.surname.value = it })
}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Password", onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
userViewModel.createUser()
navController.navigate(NavItem.Login.route)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Registration",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "Already have a account? ",
style = MaterialTheme.typography.bodyMedium
.copy(TextSecondary)
)
Text(
text = "Login",
style = MaterialTheme.typography.bodyMedium
.copy(GreenBtn),
modifier = Modifier.clickable { navController.navigate(NavItem.Login.route) }
)
}
}
}

View File

@@ -0,0 +1,65 @@
package com.example.myapplication.composeui.UIComponents
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
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.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.MaterialTheme
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.rememberUpdatedState
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.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.example.myapplication.ui.theme.BlueBorder
import com.example.myapplication.ui.theme.TextSecondary
@Composable
fun MyTextField (
label: String,
onValueChanged: (String) -> Unit,
){
val textState = remember { mutableStateOf(TextFieldValue()) }
val text by rememberUpdatedState(newValue = textState.value)
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(horizontal = 15.dp),
){
if(textState.value.text.isEmpty()){
Text(
text = label,
style = MaterialTheme.typography.bodyMedium.copy(color = TextSecondary),
)
}else{
}
BasicTextField(
value = text,
onValueChange = {
textState.value = it
onValueChanged(it.text)
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
}

View File

@@ -0,0 +1,74 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
import com.example.myapplication.ui.theme.TextSecondary
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchBar(
modifier: Modifier = Modifier,
onSearch: (String) -> Unit
) {
var searchText by remember { mutableStateOf("") }
TextField(
value = searchText,
onValueChange = {
searchText = it
onSearch(it)
},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search",
modifier = Modifier,
TextSecondary
)
},
placeholder = {
Text(stringResource(R.string.placeholder_search), style = MaterialTheme.typography.bodyMedium.copy(TextSecondary))
},
modifier = modifier
.fillMaxWidth()
.height(80.dp)
.padding(15.dp)
.clip(RoundedCornerShape(15.dp)),
singleLine = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
errorIndicatorColor = Color.Red,
),
)
}
@Preview
@Composable
fun testSearchBar(){
SearchBar(modifier = Modifier){}
}

View File

@@ -0,0 +1,42 @@
package com.example.myapplication.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.example.myapplication.model.Basket
import com.example.myapplication.model.BasketService
import com.example.myapplication.model.BasketWithServices
import kotlinx.coroutines.flow.Flow
@Dao
interface BasketDao {
@Insert
suspend fun insert(basket: Basket):Long
@Insert
suspend fun insertBasketService(basketService: BasketService)
@Query("SELECT * FROM tbl_basket WHERE creatorUserId = :id")
fun getBasketWithServices(id: Int): Flow<BasketWithServices>
@Query("SELECT * FROM tbl_basket WHERE creatorUserId = :id")
suspend fun getUsersBasket(id: Int): Basket
@Query("SELECT * FROM tbl_basket")
fun getAllBaskets(): Flow<List<Basket>>
@Delete
suspend fun delete(basket: Basket)
@Query("DELETE FROM tbl_basket_service WHERE basketId = :basketId AND serviceId = :serviceId")
suspend fun removeServiceFromBasket(basketId: Int, serviceId: Int)
@Query("UPDATE tbl_basket_service SET quantity = :quantity WHERE basketId = :basketId AND serviceId = :serviceId")
suspend fun updateServiceQuantity(basketId: Int, serviceId: Int, quantity: Int)
@Query("UPDATE tbl_basket_service SET quantity = quantity + 1 WHERE basketId = :basketId AND serviceId = :serviceId")
suspend fun incrementServiceQuantity(basketId: Int, serviceId: Int)
@Query("UPDATE tbl_basket_service SET quantity = quantity - 1 WHERE basketId = :basketId AND serviceId = :serviceId")
suspend fun decrementServiceQuantity(basketId: Int, serviceId: Int)
@Query("SELECT quantity FROM tbl_basket_service WHERE basketId = :basketId AND serviceId = :serviceId")
suspend fun getQuantity(basketId: Int, serviceId: Int): Int?
@Query("SELECT * FROM tbl_basket_service WHERE basketId = :basketId AND serviceId = :serviceId")
suspend fun getService(basketId: Int, serviceId: Int): BasketService?
@Query("SELECT SUM(price * quantity) FROM tbl_basket_service bs JOIN tbl_service s ON bs.serviceId = s.serviceId WHERE bs.basketId = :basketId")
suspend fun getTotalPriceForBasket(basketId: Int): Double?
@Query("DELETE FROM tbl_basket_service WHERE basketId = :basketId")
suspend fun clearBasket(basketId: Int)
}

View File

@@ -0,0 +1,27 @@
package com.example.myapplication.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.example.myapplication.model.Order
import com.example.myapplication.model.OrderService
import com.example.myapplication.model.OrderWithServices
import com.example.myapplication.model.UserWithOrder
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Insert
suspend fun insert(order: Order):Long
@Delete
suspend fun delete(order: Order)
@Insert
suspend fun insertOrderService(orderService: OrderService)
@Query("SELECT * FROM tbl_order WHERE orderId = :id")
fun getOrderWithServices(id: Int): Flow<OrderWithServices>
@Query("SELECT * FROM tbl_order")
fun getAllOrders(): Flow<List<Order>>
@Query("SELECT * FROM tbl_user WHERE userId =:id")
fun getUserOrders(id: Int) : Flow<UserWithOrder>
}

View File

@@ -0,0 +1,29 @@
package com.example.myapplication.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.myapplication.model.Service
import kotlinx.coroutines.flow.Flow
@Dao
interface ServiceDao {
@Insert
suspend fun insert(service: Service) : Long
@Update
suspend fun update(service: Service)
@Delete
suspend fun delete(service: Service)
@Query("SELECT*FROM tbl_service")
fun getAllServices(): Flow<List<Service>>
@Query("SELECT*FROM tbl_service")
fun getAllServicesPaged(): PagingSource<Int, Service>
@Query("SELECT * FROM tbl_service WHERE serviceId = :id")
suspend fun getServiceById(id: Int): Service
}

View File

@@ -0,0 +1,31 @@
package com.example.myapplication.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.model.User
import com.example.myapplication.model.UserWithOrder
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("SELECT * FROM tbl_user WHERE userId = :id")
suspend fun getUserById(id: Int): User
@Query("SELECT * FROM tbl_user WHERE email = :email")
suspend fun getUserByEmail(email: String): User
@Query("SELECT * FROM tbl_user WHERE userId =:id")
fun getUserOrders(id: Int) : Flow<UserWithOrder>
}

View File

@@ -0,0 +1,69 @@
package com.example.myapplication.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.dao.BasketDao
import com.example.myapplication.dao.OrderDao
import com.example.myapplication.dao.ServiceDao
import com.example.myapplication.dao.UserDao
import com.example.myapplication.model.Basket
import com.example.myapplication.model.BasketService
import com.example.myapplication.model.Order
import com.example.myapplication.model.OrderService
import com.example.myapplication.model.RoleEnum
import com.example.myapplication.model.Service
import com.example.myapplication.model.User
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Database(entities = [User::class, Service::class, Order::class, OrderService::class, Basket::class, BasketService::class], version = 7)
abstract class AppDatabase : RoomDatabase(){
abstract fun serviceDao(): ServiceDao
abstract fun userDao(): UserDao
abstract fun orderDao(): OrderDao
abstract fun basketDao(): BasketDao
companion object {
private const val DB_NAME: String = "my-db"
@Volatile
private var INSTANCE: AppDatabase? = null
suspend fun populateDatabase() {
INSTANCE?.let { database ->
// User
val userDao = database.userDao()
val user1 = User(null, "Danil", "Markov", "danil@mail.ru", "123", RoleEnum.Admin)
userDao.insert(user1)
val basketDao = database.basketDao()
val basket1 = Basket(null, user1.userId!!)
basketDao.insert(basket1)
}
}
fun getInstance(appContext: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
AppDatabase::class.java,
DB_NAME
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
populateDatabase()
}
}
})
.fallbackToDestructiveMigration()
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@@ -0,0 +1,11 @@
package com.example.myapplication.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tbl_basket")
data class Basket(
@PrimaryKey
val basketId: Int? = null,
val creatorUserId: Int
)

View File

@@ -0,0 +1,11 @@
package com.example.myapplication.model
import androidx.room.Entity
@Entity(tableName = "tbl_basket_service",
primaryKeys = ["basketId", "serviceId"])
data class BasketService(
val basketId: Int,
val serviceId: Int,
val quantity: Int
)

View File

@@ -0,0 +1,15 @@
package com.example.myapplication.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class BasketWithServices(
@Embedded val basket: Basket,
@Relation(
parentColumn = "basketId",
entityColumn = "serviceId",
associateBy = Junction(BasketService::class)
)
val services: List<Service>
)

View File

@@ -0,0 +1,13 @@
package com.example.myapplication.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tbl_order")
data class Order(
@PrimaryKey(autoGenerate = true)
val orderId: Int? = null,
val date: Long,
val total: Double,
val creatorUserId: Int
)

View File

@@ -0,0 +1,10 @@
package com.example.myapplication.model
import androidx.room.Entity
@Entity(primaryKeys = ["orderId", "serviceId"], tableName = "tbl_order_service")
data class OrderService(
val orderId: Int,
val serviceId: Int,
val quantity: Int
)

View File

@@ -0,0 +1,15 @@
package com.example.myapplication.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class OrderWithServices(
@Embedded val order: Order,
@Relation(
parentColumn = "orderId",
entityColumn = "serviceId",
associateBy = Junction(OrderService::class)
)
val services: List<Service>
)

View File

@@ -0,0 +1,6 @@
package com.example.myapplication.model
enum class RoleEnum {
Admin,
User
}

View File

@@ -0,0 +1,13 @@
package com.example.myapplication.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName="tbl_service")
data class Service (
@PrimaryKey(autoGenerate = true)
var serviceId: Int? = null,
var name: String,
var price: Double,
var photo: Int? = null
)

View File

@@ -0,0 +1,16 @@
package com.example.myapplication.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tbl_user")
data class User(
@PrimaryKey(autoGenerate = true)
val userId: Int? = null,
var name: String,
var surname: String,
var email: String,
var password: String,
val role: RoleEnum,
val photo: Int? = null
)

View File

@@ -0,0 +1,13 @@
package com.example.myapplication.model
import androidx.room.Embedded
import androidx.room.Relation
data class UserWithOrder(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "creatorUserId"
)
val orders: List<Order>
)

View File

@@ -0,0 +1,24 @@
package com.example.myapplication.repository
import com.example.myapplication.dao.BasketDao
import com.example.myapplication.model.Basket
import com.example.myapplication.model.BasketService
import com.example.myapplication.model.BasketWithServices
import kotlinx.coroutines.flow.Flow
class BasketRepository(private val basketDao: BasketDao) {
suspend fun createBasket(basket: Basket): Long = basketDao.insert(basket)
suspend fun getUsersBasket(userId: Int): Basket = basketDao.getUsersBasket(userId)
suspend fun removeServiceFromBasket(basketId: Int, serviceId: Int) = basketDao.removeServiceFromBasket(basketId, serviceId)
suspend fun updateServiceQuantity(basketId: Int, serviceId: Int, quantity: Int) = basketDao.updateServiceQuantity(basketId, serviceId, quantity)
suspend fun incrementServiceQuantity(basketId: Int, serviceId: Int) = basketDao.incrementServiceQuantity(basketId, serviceId)
suspend fun decrementServiceQuantity(basketId: Int, serviceId: Int) = basketDao.decrementServiceQuantity(basketId, serviceId)
suspend fun insertBasketService(basketService: BasketService) = basketDao.insertBasketService(basketService)
fun getBasketWithServices(id: Int): Flow<BasketWithServices> = basketDao.getBasketWithServices(id)
fun getAllBasket(): Flow<List<Basket>> = basketDao.getAllBaskets()
suspend fun delete(basket: Basket) = basketDao.delete(basket)
suspend fun getQuantity(basketId: Int, serviceId: Int): Int? = basketDao.getQuantity(basketId, serviceId)
suspend fun getService(basketId: Int, serviceId: Int): BasketService? = basketDao.getService(basketId, serviceId)
suspend fun getTotalPriceForBasket(basketId: Int): Double? = basketDao.getTotalPriceForBasket(basketId)
suspend fun clearBasket(basketId: Int) = basketDao.clearBasket(basketId)
}

View File

@@ -0,0 +1,14 @@
package com.example.myapplication.repository
import com.example.myapplication.dao.OrderDao
import com.example.myapplication.model.Order
import com.example.myapplication.model.OrderService
class OrderRepository(private val orderDao: OrderDao) {
suspend fun insert(order: Order) = orderDao.insert(order)
suspend fun insertOrderService(orderService: OrderService) = orderDao.insertOrderService(orderService)
suspend fun delete(order: Order) = orderDao.delete(order)
fun getOrderWithServices(id: Int) = orderDao.getOrderWithServices(id)
fun getAllOrders() = orderDao.getAllOrders()
fun getUserOrders(id: Int) = orderDao.getUserOrders(id)
}

View File

@@ -0,0 +1,23 @@
package com.example.myapplication.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.dao.ServiceDao
import com.example.myapplication.model.Service
import kotlinx.coroutines.flow.Flow
class ServiceRepository(private val serviceDao: ServiceDao) {
suspend fun insert(service: Service) = serviceDao.insert(service)
suspend fun update(service: Service) = serviceDao.update(service)
suspend fun delete(service: Service) = serviceDao.delete(service)
suspend fun getServiceById(id: Int) = serviceDao.getServiceById(id)
fun getAllServices() = serviceDao.getAllServices()
fun call(): Flow<PagingData<Service>> {
return Pager(
PagingConfig(pageSize = 5)
){
serviceDao.getAllServicesPaged()
}.flow
}
}

View File

@@ -0,0 +1,12 @@
package com.example.myapplication.repository
import com.example.myapplication.dao.UserDao
import com.example.myapplication.model.User
class UserRepository(private val userDao: UserDao) {
suspend fun insert(user: User) = userDao.insert(user)
suspend fun update(user: User) = userDao.update(user)
suspend fun delete(user: User) = userDao.delete(user)
suspend fun getUserById(id: Int) = userDao.getUserById(id)
suspend fun getUserByEmail(email: String) = userDao.getUserByEmail(email)
}

View File

@@ -9,3 +9,12 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val BlueMain = Color(0xFFB3E5FC)
val RedBtn = Color(0xFF960000)
val GreenBtn = Color(0xFF009688)
val TextPrimary = Color(0xFF212121)
val TextSecondary = Color(0xFF757575)
val White = Color(0xFFFFFFFF)
val BlueNavbar = Color(0xFF79D5FF)
val BlueBorder = Color(0xFF79D5FF)

View File

@@ -3,6 +3,7 @@ package com.example.myapplication.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.Typography
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
@@ -13,6 +14,7 @@ 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.compose.ui.text.TextStyle
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
@@ -38,7 +40,7 @@ private val LightColorScheme = lightColorScheme(
)
@Composable
fun MyApplicationTheme(
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,

View File

@@ -2,18 +2,19 @@ package com.example.myapplication.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.example.myapplication.R
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
bodyMedium = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_serif_regular)),
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
color = TextPrimary
)
/* Other default text styles to override
titleLarge = TextStyle(

View File

@@ -0,0 +1,27 @@
package com.example.myapplication.viewmodel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.myapplication.App
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
ServiceViewModel(app().container.serviceRepo)
}
initializer {
UserViewModel(app().container.userRepo, app().container.basketRepo)
}
initializer {
OrderViewModel(app().container.orderRepo, app().container.basketRepo)
}
initializer {
BasketViewModel(app().container.basketRepo, app().container.orderRepo,)
}
}
}
fun CreationExtras.app(): App =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as App)

View File

@@ -0,0 +1,92 @@
package com.example.myapplication.viewmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.GlobalUser
import com.example.myapplication.model.Basket
import com.example.myapplication.model.BasketService
import com.example.myapplication.model.BasketWithServices
import com.example.myapplication.repository.BasketRepository
import com.example.myapplication.repository.OrderRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class BasketViewModel(private val basketRepository: BasketRepository, private val orderRepository: OrderRepository) : ViewModel() {
private val _quantityStateMap = mutableMapOf<Int, MutableStateFlow<Int>>()
private val _total = mutableDoubleStateOf(0.00)
val total: State<Double> get() = _total
fun getQuantityState(basketId: Int, serviceId: Int): StateFlow<Int> {
val quantityStateFlow = _quantityStateMap.getOrPut(serviceId) {
MutableStateFlow(0)
}
viewModelScope.launch {
val quantityFromDb = basketRepository.getQuantity(basketId, serviceId)
quantityFromDb?.let { quantityStateFlow.value = it }
}
return quantityStateFlow
}
suspend fun isServiceInBasket(basketId: Int, serviceId: Int): Boolean {
return basketRepository.getService(basketId, serviceId) != null
}
fun addToBasket(basketServices: BasketService) = viewModelScope.launch {
val isServiceInBasket = isServiceInBasket(basketServices.basketId, basketServices.serviceId)
if (isServiceInBasket) {
incrementQuantity(basketServices.basketId, basketServices.serviceId)
} else {
basketRepository.insertBasketService(basketServices)
}
}
fun getBasketServices(id: Int): Flow<BasketWithServices> {
return basketRepository.getBasketWithServices(id)
}
suspend fun getUsersBasket(id: Int): Basket {
return basketRepository.getUsersBasket(id)
}
fun deleteServiceFromBasket(basketId: Int, serviceId: Int) = viewModelScope.launch {
basketRepository.removeServiceFromBasket(basketId, serviceId)
}
fun incrementQuantity(basketId: Int, serviceId: Int) {
val currentQuantity = _quantityStateMap[serviceId]?.value ?: 1
_quantityStateMap[serviceId]?.value = currentQuantity + 1
viewModelScope.launch {
basketRepository.incrementServiceQuantity(basketId, serviceId)
updateSubTotal(GlobalUser.getInstance().getUser()?.userId!!)
}
}
fun decrementQuantity(basketId: Int, serviceId: Int) {
val currentQuantity = _quantityStateMap[serviceId]?.value ?: 1
if (currentQuantity > 1) {
_quantityStateMap[serviceId]?.value = currentQuantity - 1
viewModelScope.launch {
basketRepository.decrementServiceQuantity(basketId, serviceId)
updateSubTotal(GlobalUser.getInstance().getUser()?.userId!!)
}
}
}
fun updateSubTotal(userId: Int) {
viewModelScope.launch {
_total.value = getTotal(userId)
}
}
suspend fun getTotal(userId: Int): Double {
return basketRepository.getTotalPriceForBasket(basketRepository.getUsersBasket(userId!!).basketId!!) ?: 0.00
}
}

View File

@@ -0,0 +1,70 @@
package com.example.myapplication.viewmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.GlobalUser
import com.example.myapplication.model.Order
import com.example.myapplication.model.OrderService
import com.example.myapplication.model.OrderWithServices
import com.example.myapplication.model.Service
import com.example.myapplication.model.UserWithOrder
import com.example.myapplication.repository.BasketRepository
import com.example.myapplication.repository.OrderRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import java.util.Date
class OrderViewModel(private val orderRepository: OrderRepository, private val basketRepository: BasketRepository) : ViewModel() {
private var _selectedItems = MutableLiveData<List<Service>>()
val selectedItems get() = _selectedItems
private val _total = mutableDoubleStateOf(0.00)
val total: State<Double> get() = _total
fun createOrder() = viewModelScope.launch {
val userId = GlobalUser.getInstance().getUser()?.userId!!
val order = Order(
date = Date().time,
total = getTotal(basketRepository.getUsersBasket(userId).basketId!!),
creatorUserId = GlobalUser.getInstance().getUser()?.userId!!
)
var orderId = orderRepository.insert(order)
for(service in selectedItems.value!!){
val quantity = basketRepository.getQuantity(basketRepository.getUsersBasket(userId).basketId!!, service.serviceId!!)
val orderService = OrderService( orderId.toInt(), service.serviceId!!, quantity!!)
if (orderService != null) {
orderRepository.insertOrderService(orderService)
}
}
basketRepository.clearBasket(basketRepository.getUsersBasket(userId).basketId!!)
}
fun getOrderWithServices(id: Int) : Flow<OrderWithServices> {
return orderRepository.getOrderWithServices(id)
}
fun getUserOrders(id: Int): Flow<UserWithOrder> {
return orderRepository.getUserOrders(id)
}
fun updateSelectedItems(items: List<Service>) {
_selectedItems.value = items
val userId = GlobalUser.getInstance().getUser()?.userId!!
updateSubTotal(userId)
}
fun updateSubTotal(userId: Int) {
viewModelScope.launch {
_total.value = getTotal(userId)
}
}
suspend fun getTotal(basketId: Int): Double {
return basketRepository.getTotalPriceForBasket(basketId) ?: 0.00
}
}

View File

@@ -0,0 +1,45 @@
package com.example.myapplication.viewmodel
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
import com.example.myapplication.R
import com.example.myapplication.model.Service
import com.example.myapplication.repository.ServiceRepository
import kotlinx.coroutines.launch
class ServiceViewModel(private val serviceRepository: ServiceRepository): ViewModel() {
var name = mutableStateOf("")
var price = mutableDoubleStateOf(0.00)
var photo = mutableIntStateOf(R.drawable.image_service)
var service = mutableStateOf<Service>(Service(null,"", 0.0, null))
val serviceList = serviceRepository.call().cachedIn(viewModelScope)
fun createService() = viewModelScope.launch {
val service = Service(
name = name.value,
price = price.doubleValue,
photo = photo.intValue
)
serviceRepository.insert(service)
}
fun updateService() = viewModelScope.launch {
serviceRepository.update(service.value)
}
fun deleteService(service: Service) = viewModelScope.launch {
serviceRepository.delete(service)
}
fun getServiceById(id: Int) = viewModelScope.launch {
serviceRepository.getServiceById(id)
}
fun getAllServices() = viewModelScope.launch {
serviceRepository.getAllServices()
}
}

View File

@@ -0,0 +1,73 @@
package com.example.myapplication.viewmodel
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.GlobalUser
import com.example.myapplication.R
import com.example.myapplication.model.Basket
import com.example.myapplication.model.RoleEnum
import com.example.myapplication.model.User
import com.example.myapplication.repository.BasketRepository
import com.example.myapplication.repository.UserRepository
import kotlinx.coroutines.launch
class UserViewModel(private val userRepository: UserRepository, private val basketRepository: BasketRepository): ViewModel() {
var name = mutableStateOf("")
var surname = mutableStateOf("")
var email = mutableStateOf("")
var password = mutableStateOf("")
var photo = mutableIntStateOf(R.drawable.icon_profile)
fun createUser() = viewModelScope.launch {
var user = User(
name = name.value,
surname = surname.value,
email = email.value,
password = password.value,
role = RoleEnum.User
)
userRepository.insert(user)
}
fun authUser() = viewModelScope.launch {
val user = userRepository.getUserByEmail(email.value)
if(password.value.isNotEmpty() && user.password == password.value){
val globalUser = GlobalUser.getInstance()
globalUser.setUser(user)
val basket = basketRepository.getUsersBasket(user.userId!!)
if(basket == null){
basketRepository.createBasket(Basket(null, user.userId))
}
}
}
fun updateUser() = viewModelScope.launch {
var updateUser = GlobalUser.getInstance().getUser()
if(email.value != "")
updateUser?.email = email.value
else
updateUser?.email = updateUser?.email.toString()
if(name.value != "")
updateUser?.name = name.value
else
updateUser?.name = updateUser?.name.toString()
if(surname.value != "")
updateUser?.surname = surname.value
else
updateUser?.surname = updateUser?.surname.toString()
if(password.value != "")
updateUser?.password = password.value
else
updateUser?.password = updateUser?.password.toString()
//updateUser?.photo =
if (updateUser != null) {
userRepository.update(updateUser)
}
}
fun isValidEmail(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email.value).matches()
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="41dp"
android:height="50dp"
android:viewportWidth="41"
android:viewportHeight="50">
<path
android:pathData="M35.372,10H30.319C30.319,4.475 25.796,0 20.213,0C14.629,0 10.106,4.475 10.106,10H5.053C2.274,10 0,12.25 0,15V45C0,47.75 2.274,50 5.053,50H35.372C38.152,50 40.425,47.75 40.425,45V15C40.425,12.25 38.152,10 35.372,10ZM20.213,5C22.992,5 25.266,7.25 25.266,10H15.16C15.16,7.25 17.434,5 20.213,5ZM35.372,45H5.053V15H10.106V20C10.106,21.375 11.243,22.5 12.633,22.5C14.023,22.5 15.16,21.375 15.16,20V15H25.266V20C25.266,21.375 26.403,22.5 27.793,22.5C29.182,22.5 30.319,21.375 30.319,20V15H35.372V45Z"
android:fillColor="#009688"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="44dp"
android:viewportWidth="42"
android:viewportHeight="44">
<path
android:pathData="M4.167,16.667H33.333V20.833H37.5V8.333C37.5,6.042 35.625,4.167 33.333,4.167H31.25V0H27.083V4.167H10.417V0H6.25V4.167H4.167C1.854,4.167 0.021,6.042 0.021,8.333L0,37.5C0,39.792 1.854,41.667 4.167,41.667H18.75V37.5H4.167V16.667ZM4.167,8.333H33.333V12.5H4.167V8.333ZM41.333,29.75L39.854,31.229L35.438,26.813L36.917,25.333C37.729,24.521 39.042,24.521 39.854,25.333L41.333,26.813C42.146,27.625 42.146,28.938 41.333,29.75ZM33.958,28.292L38.375,32.708L27.333,43.75H22.917V39.333L33.958,28.292Z"
android:fillColor="#009688"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="55dp"
android:height="44dp"
android:viewportWidth="55"
android:viewportHeight="44">
<path
android:strokeWidth="1"
android:pathData="M33.024,30.721L32.74,30.438L32.401,30.653C30.58,31.815 28.418,32.5 26.097,32.5C19.554,32.5 14.25,27.197 14.25,20.625C14.25,14.054 19.554,8.75 26.125,8.75C32.696,8.75 38,14.054 38,20.625C38,22.944 37.316,25.08 36.152,26.931L35.939,27.27L36.222,27.552L42.577,33.881L39.38,37.078L33.024,30.721ZM49.5,39H50V38.5V5.5V5H49.5H5.5H5V5.5V38.5V39H5.5H49.5ZM33.5,20.625C33.5,16.554 30.196,13.25 26.125,13.25C22.054,13.25 18.75,16.554 18.75,20.625C18.75,24.696 22.054,28 26.125,28C30.196,28 33.5,24.696 33.5,20.625ZM5.5,0.5H49.5C52.249,0.5 54.5,2.751 54.5,5.5V38.5C54.5,41.249 52.249,43.5 49.5,43.5H5.5C2.751,43.5 0.5,41.249 0.5,38.5V5.5C0.5,2.751 2.751,0.5 5.5,0.5Z"
android:fillColor="#009688"
android:strokeColor="#009688"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="50dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:pathData="M25,0C11.2,0 0,11.2 0,25C0,38.8 11.2,50 25,50C38.8,50 50,38.8 50,25C50,11.2 38.8,0 25,0ZM13.375,41.25C16.65,38.9 20.65,37.5 25,37.5C29.35,37.5 33.35,38.9 36.625,41.25C33.35,43.6 29.35,45 25,45C20.65,45 16.65,43.6 13.375,41.25ZM40.35,37.8C36.125,34.5 30.8,32.5 25,32.5C19.2,32.5 13.875,34.5 9.65,37.8C6.75,34.325 5,29.875 5,25C5,13.95 13.95,5 25,5C36.05,5 45,13.95 45,25C45,29.875 43.25,34.325 40.35,37.8Z"
android:fillColor="#009688"/>
<path
android:pathData="M25,10C20.175,10 16.25,13.925 16.25,18.75C16.25,23.575 20.175,27.5 25,27.5C29.825,27.5 33.75,23.575 33.75,18.75C33.75,13.925 29.825,10 25,10ZM25,22.5C22.925,22.5 21.25,20.825 21.25,18.75C21.25,16.675 22.925,15 25,15C27.075,15 28.75,16.675 28.75,18.75C28.75,20.825 27.075,22.5 25,22.5Z"
android:fillColor="#009688"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="69dp"
android:height="83dp"
android:viewportWidth="69"
android:viewportHeight="83">
<path
android:pathData="M68.042,29.521H48.875V0.771H20.125V29.521H0.958L34.5,63.063L68.042,29.521ZM29.708,39.104V10.354H39.292V39.104H44.898L34.5,49.502L24.102,39.104H29.708ZM0.958,72.646H68.042V82.229H0.958V72.646Z"
android:fillColor="#009688"/>
</vector>

View File

@@ -1,3 +1,5 @@
<resources>
<string name="app_name">My Application</string>
<string name="app_name">PetMed</string>
<string name="not_auth_error">Please login</string>
<string name="placeholder_search">Search…</string>
</resources>