Compare commits

...

51 Commits

Author SHA1 Message Date
ElEgEv
20d2c1999d Writing course work. 2023-12-28 01:17:45 +04:00
ElEgEv
814364fccd Moving files. 2023-12-26 23:14:45 +04:00
ElEgEv
201db3401f Purchase is working! 2023-12-25 01:04:38 +04:00
ElEgEv
dd05c256d9 Чё-то не отсылается. 2023-12-24 22:32:05 +04:00
ElEgEv
e0575621c4 Шишь 2023-12-24 22:27:25 +04:00
ElEgEv
0db26bb7a2 Report is working. 2023-12-24 13:16:15 +04:00
ElEgEv
2b69f0fc24 First big update :) 2023-12-22 22:24:57 +04:00
ElEgEv
2fdc2d5ba3 Отчёты. 2023-12-16 17:22:04 +04:00
ElEgEv
b22c7bf3d1 . 2023-12-16 16:29:54 +04:00
ElEgEv
9153a2d77b . 2023-12-16 16:28:35 +04:00
ElEgEv
57ecc27364 LabWork05 2023-12-12 17:10:15 +04:00
ElEgEv
c9fb6f4f33 Промежуточное 2023-12-12 09:24:41 +04:00
ElEgEv
348e6a0b57 Некоторые продвижения 2023-12-12 02:12:56 +04:00
ElEgEv
40f2191753 Server 2023-12-12 01:08:10 +04:00
ElEgEv
9fea8e3bc4 Чё-то делается 2023-12-12 01:06:48 +04:00
ElEgEv
178d0953d3 Start work. 2023-12-11 17:18:00 +04:00
ElEgEv
61d31fee9c Filter is working! 2023-12-11 17:08:55 +04:00
ElEgEv
b0fbd2704f It's working!!! 2023-12-11 16:09:28 +04:00
ElEgEv
8f116e2005 Промежуточное 2023-12-11 15:29:25 +04:00
ElEgEv
38cb371048 Почтиииии 2023-12-11 13:56:24 +04:00
ElEgEv
91a0de0cc9 Вывод по нациям 2023-12-11 11:00:14 +04:00
ElEgEv
f79449f90e Update tank is working! 2023-12-10 21:49:27 +04:00
ElEgEv
a530e3b2fe Уже страшновато. 2023-12-10 15:02:49 +04:00
ElEgEv
3860540d9c Create drop down. 2023-12-07 23:49:07 +04:00
ElEgEv
5520140746 Почти готово. 2023-11-28 11:22:11 +04:00
ElEgEv
0f2db13184 Определённые продвижения 2023-11-28 10:17:11 +04:00
ElEgEv
ba4be87c09 Работаем над конструктором. 2023-11-27 23:12:32 +04:00
ElEgEv
bf6f3fd2e4 Исправил вывод в Ангаре и запрос к БД. 2023-11-27 20:54:27 +04:00
ElEgEv
434cb03cac Аккаунт редактируется. 2023-11-27 19:12:50 +04:00
ElEgEv
8ff030127a Дело пошло)))0) 2023-11-26 23:01:25 +04:00
ElEgEv
895fdee643 Добрался до первого компонента. 2023-11-26 15:17:58 +04:00
ElEgEv
5b26f60938 Start lab work 4. 2023-11-26 13:56:51 +04:00
ElEgEv
9c86858b71 Отчёты. 2023-11-19 21:06:42 +04:00
ElEgEv
016aa33363 . 2023-11-19 20:33:46 +04:00
ElEgEv
f7148c8cd5 Update Nation.kt 2023-11-19 20:12:09 +04:00
ElEgEv
ca8ecfa033 ОНО РАБОТАЕТ. 2023-11-14 10:39:48 +04:00
ElEgEv
3e81ac477d Промежуточное. 2023-11-13 21:32:01 +04:00
ElEgEv
552b40b3d9 Потихоньку движемся 2023-11-13 13:27:23 +04:00
ElEgEv
498aa7d955 Правки 2023-11-12 00:39:21 +04:00
ElEgEv
976a08c596 Логи 2023-11-11 21:26:22 +04:00
ElEgEv
ef650da9cf Оно заработало. Привет 17 Java. 2023-11-11 21:26:08 +04:00
ElEgEv
4ab7b39d8d Началась ересь. 2023-11-11 20:11:39 +04:00
ElEgEv
c059f4fe77 Add dao models. 2023-11-11 18:14:10 +04:00
ElEgEv
63207fadd8 Add base models. 2023-11-11 17:29:21 +04:00
ElEgEv
542b673a1f Add base models. 2023-11-11 17:29:11 +04:00
ElEgEv
223837ee69 Настроил зависимости. 2023-11-11 15:01:28 +04:00
ElEgEv
04e7b52eaa . 2023-11-11 14:54:03 +04:00
ElEgEv
1f0691f231 . 2023-10-31 10:02:12 +04:00
ElEgEv
23cc45b7d6 . 2023-10-31 10:01:47 +04:00
ElEgEv
acbfda073c Переместил 2023-10-31 09:58:40 +04:00
ElEgEv
087857a6d2 Lab Work 2. 2023-10-31 09:57:39 +04:00
187 changed files with 8857 additions and 0 deletions

9
.idea/TanksApp.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/TanksApp.iml" filepath="$PROJECT_DIR$/.idea/TanksApp.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

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

58
.idea/workspace.xml Normal file
View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="fb78b956-3834-46c2-9adb-5184c664a069" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/compose/.gradle/8.0/executionHistory/executionHistory.bin" beforeDir="false" afterPath="$PROJECT_DIR$/compose/.gradle/8.0/executionHistory/executionHistory.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/.gradle/8.0/executionHistory/executionHistory.lock" beforeDir="false" afterPath="$PROJECT_DIR$/compose/.gradle/8.0/executionHistory/executionHistory.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/.gradle/8.0/fileHashes/fileHashes.bin" beforeDir="false" afterPath="$PROJECT_DIR$/compose/.gradle/8.0/fileHashes/fileHashes.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/.gradle/8.0/fileHashes/fileHashes.lock" beforeDir="false" afterPath="$PROJECT_DIR$/compose/.gradle/8.0/fileHashes/fileHashes.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/.gradle/8.0/fileHashes/resourceHashesCache.bin" beforeDir="false" afterPath="$PROJECT_DIR$/compose/.gradle/8.0/fileHashes/resourceHashesCache.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/.gradle/buildOutputCleanup/buildOutputCleanup.lock" beforeDir="false" afterPath="$PROJECT_DIR$/compose/.gradle/buildOutputCleanup/buildOutputCleanup.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/api/ServerService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/api/ServerService.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/api/model/UserTankCrossRefRemote.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/api/model/UserTankCrossRefRemote.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/api/repository/RestTankRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/api/repository/RestTankRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/TankList.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/TankList.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/edit/BuyTankViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/edit/PurchaseTankViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppDatabase.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppDatabase.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/UserTankCrossRef.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/UserTankCrossRef.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/Hangar.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/Hangar.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/ui/AppViewModelProvider.kt" beforeDir="false" afterPath="$PROJECT_DIR$/compose/app/src/main/java/ru/ulstu/is/pmu/ui/AppViewModelProvider.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/compose/server/data.json" beforeDir="false" afterPath="$PROJECT_DIR$/compose/server/data.json" 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="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="2a0MeoYvDLG6t8MsKUe7MRh0mGa" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"cidr.known.project.marker": "true",
"last_opened_file_path": "C:/Users/egore/Desktop/MyProjects/ULSTU/TankAppMobile/TanksApp/compose"
}
}]]></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="fb78b956-3834-46c2-9adb-5184c664a069" name="Changes" comment="" />
<created>1703451140424</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1703451140424</updated>
</task>
<servers />
</component>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

View File

@ -0,0 +1,2 @@
#Mon Oct 02 21:58:30 GMT+04:00 2023
gradle.version=8.0

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

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

1
compose/.idea/.name Normal file
View File

@ -0,0 +1 @@
pmu-demo

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="vectorWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="vectorAssetStep">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipartAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/drive_file_rename_outline/baseline_drive_file_rename_outline_24.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="outputName" value="baseline_drive_file_rename_outline_24" />
<entry key="sourceFile" value="C:\Users\egore\Desktop\MyProjects\ULSTU\TankAppMobile\TanksApp\compose" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

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

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

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

View File

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

View File

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

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

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

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/pmu-demo.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/pmu-demo.app.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/pmu-demo.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/pmu-demo.app.main.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../../../app/src/androidTest">
<sourceFolder url="file://$MODULE_DIR$/../../../app/src/androidTest/assets" type="java-test-resource" />
</content>
</component>
</module>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../../../app/src/main">
<sourceFolder url="file://$MODULE_DIR$/../../../app/src/main/assets" type="java-resource" />
</content>
</component>
</module>

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

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

1
compose/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,100 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "ru.ulstu.is.pmu"
compileSdk = 34
defaultConfig {
applicationId = "ru.ulstu.is.pmu"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildToolsVersion = "34.0.0"
}
kotlin {
jvmToolchain(17)
}
dependencies {
// Core
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
// UI
implementation("androidx.activity:activity-compose:1.8.0")
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.navigation:navigation-compose:2.7.5")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
// Room
val room_version = "2.6.0"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
implementation("androidx.room:room-paging:$room_version")
// Paging | +
val pagingVersion = "3.3.0-alpha02"
implementation("androidx.paging:paging-compose:$pagingVersion")
// retrofit
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("androidx.paging:paging-compose:3.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
// Tests
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}

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

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

View File

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

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".TankApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:networkSecurityConfig="@xml/network_security_config"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Pmudemo"
tools:targetApi="31">
<activity
android:name=".MainComposeActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Pmudemo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 KiB

View File

@ -0,0 +1,46 @@
package ru.ulstu.`is`.pmu
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.rememberNavController
import ru.ulstu.`is`.pmu.composeui.navigation.NavGraph
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
class MainComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PmudemoTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val navController = rememberNavController()
NavGraph(navController = navController)
}
}
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun MainNavbarPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
val navController = rememberNavController()
NavGraph(navController = navController)
}
}
}

View File

@ -0,0 +1,14 @@
package ru.ulstu.`is`.pmu
import android.app.Application
import ru.ulstu.`is`.pmu.tank.database.AppContainer
import ru.ulstu.`is`.pmu.tank.database.AppDataContainer
class TankApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
}

View File

@ -0,0 +1,302 @@
package ru.ulstu.`is`.pmu.composeui.navigation
import android.annotation.SuppressLint
import android.content.res.Configuration
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.navigation.NavController
import ru.ulstu.`is`.pmu.screen.composeui.TankList
import ru.ulstu.`is`.pmu.screen.composeui.Account
import ru.ulstu.`is`.pmu.screen.composeui.Constructor
import ru.ulstu.`is`.pmu.screen.composeui.Hangar
import ru.ulstu.`is`.pmu.screen.composeui.NationList
import ru.ulstu.`is`.pmu.screen.composeui.Report
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Topbar(
navController: NavHostController,
currentScreen: Screen?
) {
TopAppBar(
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = CustomOrange,
titleContentColor = CustomDark,
),
title = {
Text(
stringResource(currentScreen?.resourceId ?: R.string.app_name),
fontSize = 40.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
},
navigationIcon = {
if (
navController.previousBackStackEntry != null
&& (currentScreen == null || !currentScreen.showInBottomBar)
) {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimary
)
}
}
}
)
}
@Composable
fun Navbar(
navController: NavHostController,
currentDestination: NavDestination?,
modifier: Modifier = Modifier.background(CustomOrange)
) {
NavigationBar(
modifier,
containerColor = CustomOrange
)
{
Screen.bottomBarItems.forEach { screen ->
NavigationBarItem(
modifier = Modifier.background(CustomOrange),
icon = { Icon(screen.icon, contentDescription = null, tint = CustomDark, modifier = Modifier.size(50.dp)) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
@Composable
fun Navhost(
navController: NavHostController,
) {
NavHost(
navController,
startDestination = Screen.TankList.route,
) {
composable(Screen.TankList.route) { TankList(navController) }
composable(Screen.NATIONS.route) { NationList(navController) }
composable(
Screen.Constructor.route,
arguments = listOf(navArgument("id") { type = NavType.LongType })
) { Constructor(navController) }
composable(Screen.Report.route) { Report(navController) }
composable(Screen.Hangar.route) { Hangar(navController) }
composable(Screen.Account.route) { Account(navController) }
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DropDownList(
navController: NavHostController,
){
//для работы выпадающего списка уровней
val optionsList = listOf(
"Нациям",
"Уровням"
)
var expandedOptions by remember { mutableStateOf(false) }
var mySelectedOption by remember { mutableStateOf(optionsList[0]) }
Row(
horizontalArrangement = Arrangement.spacedBy(40.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(10.dp, 10.dp, 10.dp, 0.dp)
.height(70.dp)
){
Text(text = stringResource(id = R.string.main_label), color = Color.White, textAlign = TextAlign.Center)
// Выпадающий список вариантов показа
ExposedDropdownMenuBox(
expanded = expandedOptions,
onExpandedChange = {
expandedOptions = !expandedOptions
}
) {
// textfield
TextField(
modifier = Modifier
.menuAnchor() // menuAnchor modifier must be passed to the text field for correctness.
.width(200.dp),
readOnly = true,
value = mySelectedOption,
onValueChange = { mySelectedOption = it },
label = { Text("") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedOptions) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
// menu
ExposedDropdownMenu(
expanded = expandedOptions,
onDismissRequest = {
expandedOptions = false
},
) {
// menu items
optionsList.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
onClick = {
mySelectedOption = selectionOption
expandedOptions = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}
}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainNavbar(
navController: NavController
) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val currentScreen = currentDestination?.route?.let { Screen.getItem(it) }
Scaffold(
topBar = {
Topbar(navController, currentScreen)
},
bottomBar = {
if (currentScreen == null || currentScreen.showInBottomBar) {
Navbar(navController, currentDestination)
}
},
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.background(CustomDark)
.fillMaxSize()
.padding(0.dp, 70.dp, 0.dp, 170.dp),
){
if(currentScreen?.route == Screen.TankList.route){
DropDownList(navController)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceAround
){
Button(
modifier = Modifier
.width(180.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = CustomDark),
onClick = {
val route = Screen.Constructor.route.replace("{id}", 0.toString())
navController.navigate(route)
}
) {
Text(text = "Добавить")
}
Button(
modifier = Modifier
.width(180.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = CustomDark),
onClick = {
navController.navigate(Screen.Report.route)
}
) {
Text("Отчёты")
}
}
}
Navhost(navController)
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun MainNavbarPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
val navController = rememberNavController()
NavGraph(navController = navController)
}
}
}

View File

@ -0,0 +1,26 @@
package ru.ulstu.`is`.pmu.composeui.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import ru.ulstu.`is`.pmu.screen.composeui.Login
import ru.ulstu.`is`.pmu.screen.composeui.Register
@Composable
fun NavGraph (navController: NavHostController){
NavHost(
navController = navController,
startDestination = Screen.Login.route)
{
composable(route = Screen.Login.route){
Login(navController)
}
composable(route = Screen.Register.route){
Register(navController)
}
composable(route = Screen.TankList.route){
MainNavbar(navController)
}
}
}

View File

@ -0,0 +1,62 @@
package ru.ulstu.`is`.pmu.composeui.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.List
import androidx.compose.ui.graphics.vector.ImageVector
import ru.ulstu.`is`.pmu.R
enum class Screen(
val route: String,
@StringRes val resourceId: Int,
val icon: ImageVector = Icons.Filled.Favorite,
val showInBottomBar: Boolean = true
) {
TankList(
"tank-list", R.string.tanks_main_title, Icons.Filled.Home
),
Login(
"login", R.string.login, showInBottomBar = false
),
Register(
"register", R.string.register, showInBottomBar = false
),
Constructor(
"constructor/{id}", R.string.constructor, Icons.Filled.Add
),
Hangar(
"hangar", R.string.hangar, Icons.Filled.List
),
Account(
"account", R.string.account, Icons.Filled.AccountCircle
),
Report(
"report", R.string.report, showInBottomBar = false
),
NATIONS("nations", R.string.nation, Icons.Filled.Create),
EDIT_NATIONS("edit-nation/{id}", R.string.nation);
companion object {
val bottomBarItems = listOf(
TankList,
NATIONS,
Hangar,
Account
)
fun getItem(route: String): Screen? {
val findRoute = route.split("/").first()
return values().find { value -> value.route.startsWith(findRoute) }
}
fun getEntityRoute(screen: Screen, itemId: Long): String {
return screen.route.replace("{id}", itemId.toString())
}
}
}

View File

@ -0,0 +1,187 @@
package ru.ulstu.`is`.pmu.screen.composeui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.tank.composeui.edit.UserDetails
import ru.ulstu.`is`.pmu.tank.composeui.edit.UserEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.UserUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.toUiState
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.CustomRed
import ru.ulstu.`is`.pmu.ui.theme.CustomYellow
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
//теперь стартует здесь
@Composable
fun Account(
navController: NavController,
viewModel: UserEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val coroutineScope = rememberCoroutineScope()
Account(
userUiState = viewModel.userUiState,
onClick = {
coroutineScope.launch {
viewModel.saveUser()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Account(
userUiState: UserUiState,
onClick: () -> Unit,
onUpdate: (UserDetails) -> Unit
) {
//для работы текстовых полей
var nickname by remember { mutableStateOf(userUiState.userDetails.nickname) }
var password by remember { mutableStateOf(userUiState.userDetails.password) }
var balance by remember { mutableStateOf(userUiState.userDetails.balance.toString()) }
Column(
verticalArrangement = Arrangement.spacedBy(35.dp),
modifier = Modifier
.fillMaxHeight(1f)
.padding(0.dp, 15.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(CustomYellow)
){
Column {
Spacer(Modifier.height(15.dp))
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
){
Text(text="Личные данные", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Spacer(Modifier.height(15.dp))
Row(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier.fillMaxWidth()
){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(0.dp, 5.dp)
) {
Text(text="Логин:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(30.dp))
Text(text="Пароль:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(30.dp))
Text(text="Баланс:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Column {
TextField(
value = userUiState.userDetails.nickname,
onValueChange = { onUpdate(userUiState.userDetails.copy(nickname = it)) },
modifier = Modifier
.width(200.dp),
)
Spacer(Modifier.height(10.dp))
TextField(
value = userUiState.userDetails.password,
onValueChange = { onUpdate(userUiState.userDetails.copy(password = it)) },
modifier = Modifier
.width(200.dp),
)
Spacer(Modifier.height(10.dp))
TextField(
value = userUiState.userDetails.balance.toString(),
onValueChange = { onUpdate(userUiState.userDetails.copy(balance = it.toInt())) },
modifier = Modifier
.width(200.dp),
colors = TextFieldDefaults.textFieldColors(
focusedLabelColor = CustomOrange,
cursorColor = CustomOrange
)
)
}
}
Spacer(Modifier.height(25.dp))
}
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 10.dp)
){
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomRed,
contentColor = Color.White
),
onClick = { onClick }) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.save_account_button), fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
}
Spacer(Modifier.weight(1.0f))
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun AccountEditPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
Account(
userUiState = User.getUser().toUiState(true),
onClick = {},
onUpdate = {}
)
}
}
}

View File

@ -0,0 +1,528 @@
package ru.ulstu.`is`.pmu.screen.composeui
import android.content.res.Configuration
import android.graphics.Bitmap
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults.TrailingIcon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.application.ui.miniatureBound
import com.application.ui.resize
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.tank.composeui.edit.LevelDropDownViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.LevelUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.LevelsListUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationDropDownViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationsListUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.TankDetails
import ru.ulstu.`is`.pmu.tank.composeui.edit.TankEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.TankUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.toUiState
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.screen.composeui.image.CuteImageUploader
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.CustomYellow
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun Constructor(
navController: NavController,
tankEditViewModel: TankEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
levelDropDownViewModel: LevelDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory),
nationDropDownViewModel: NationDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val coroutineScope = rememberCoroutineScope()
val tankId = tankEditViewModel.getTankId()
levelDropDownViewModel.setCurrentLevel(tankEditViewModel.tankUiState.tankDetails.levelId ?: 1)
nationDropDownViewModel.setCurrentNation(tankEditViewModel.tankUiState.tankDetails.nationId ?: 1)
Constructor(
tankId = tankId,
tankViewModel = tankEditViewModel,
tankUiState = tankEditViewModel.tankUiState,
levelUiState = levelDropDownViewModel.levelUiState,
levelsListUiState = levelDropDownViewModel.levelsListUiState,
onLevelUpdate = levelDropDownViewModel::updateUiState,
nationUiState = nationDropDownViewModel.nationUiState,
nationsListUiState = nationDropDownViewModel.nationsListUiState,
onNationUpdate = nationDropDownViewModel::updateUiState,
onClickSave = {
coroutineScope.launch {
tankEditViewModel.saveTank()
navController.popBackStack()
}
},
onClickDelete = {
coroutineScope.launch {
tankEditViewModel.deleteTank()
navController.popBackStack()
}
},
onUpdate = tankEditViewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LevelDropDown(
levelUiState: LevelUiState,
levelsListUiState: LevelsListUiState,
onLevelUpdate: (Level) -> Unit
) {
var expanded: Boolean by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
value = levelUiState.level?.level.toString(),
onValueChange = {},
readOnly = true,
trailingIcon = {
TrailingIcon(expanded = expanded)
},
modifier = Modifier
.width(200.dp)
.menuAnchor()
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.White)
.exposedDropdownSize()
) {
levelsListUiState.levelList.forEach { level ->
DropdownMenuItem(
text = {
Text(text = level.level.toString())
},
onClick = {
onLevelUpdate(level)
expanded = false
}
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NationDropDown(
nationUiState: NationUiState,
nationsListUiState: NationsListUiState,
onNationUpdate: (Nation) -> Unit
) {
var expanded: Boolean by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
value = nationUiState.nation?.nationName ?: "",
onValueChange = {},
readOnly = true,
trailingIcon = {
TrailingIcon(expanded = expanded)
},
modifier = Modifier
.width(200.dp)
.menuAnchor()
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.White)
.exposedDropdownSize()
) {
nationsListUiState.nationList.forEach { nation ->
DropdownMenuItem(
text = {
Text(text = nation.nationName)
},
onClick = {
onNationUpdate(nation)
expanded = false
}
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Constructor(
tankId: Long,
tankViewModel: TankEditViewModel,
tankUiState: TankUiState,
levelUiState: LevelUiState,
levelsListUiState: LevelsListUiState,
onLevelUpdate: (Level) -> Unit,
nationUiState: NationUiState,
nationsListUiState: NationsListUiState,
onNationUpdate: (Nation) -> Unit,
onClickSave: () -> Unit,
onClickDelete: () -> Unit,
onUpdate: (TankDetails) -> Unit
) {
fun handleImageUpload(bitmap: Bitmap) {
tankViewModel.updateUiState(
tankUiState.tankDetails.copy(
miniature = bitmap.resize(miniatureBound),
)
)
}
Column(
verticalArrangement = Arrangement.spacedBy(35.dp),
modifier = Modifier.padding(0.dp, 15.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(CustomYellow)
){
Column {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
){
Text(text="Добавить танк", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Spacer(Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(5.dp)
){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(0.dp, 5.dp)
) {
Text(
text = "Название:",
fontSize = 30.sp,
color = Color.Black,
fontWeight = FontWeight.Bold
)
}
TextField(
value = tankUiState.tankDetails.name,
placeholder = { Text(text = "Название", color = CustomDark) },
onValueChange = { onUpdate(tankUiState.tankDetails.copy(name = it)) },
modifier = Modifier
.width(200.dp),
)
}
Spacer(Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(5.dp)
){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(0.dp, 5.dp)
) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
){
Text(text="Изображение", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
CuteImageUploader(
bitmap = tankUiState.tankDetails.miniature,
onResult = { bitmap: Bitmap -> handleImageUpload(bitmap) }
)
}
}
Spacer(Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(5.dp)
){
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(0.dp, 5.dp).fillMaxHeight()
) {
Text(text="Уровень:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
// Выпадающий список уровней
LevelDropDown(
levelUiState = levelUiState,
levelsListUiState = levelsListUiState,
onLevelUpdate = {
onUpdate(tankUiState.tankDetails.copy(levelId = it.uid))
onLevelUpdate(it)
},
)
}
Spacer(Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(5.dp)
){
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(0.dp, 5.dp).fillMaxHeight()
) {
Text(text="Нация:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
// Выпадающий список наций
NationDropDown(
nationUiState = nationUiState,
nationsListUiState = nationsListUiState,
onNationUpdate = {
onUpdate(tankUiState.tankDetails.copy(nationId = it.uid))
onNationUpdate(it)
},
)
}
Spacer(Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(5.dp)
){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(0.dp, 5.dp)
) {
Text(text="Стоимость:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
TextField(
value = tankUiState.tankDetails.price.toString(),
placeholder = { Text(text = "Стоимость", color = CustomDark) },
onValueChange = { onUpdate(tankUiState.tankDetails.copy(price = it.toInt())) },
modifier = Modifier
.width(200.dp),
)
}
Spacer(Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 10.dp)
){
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = Color.Black
),
enabled = tankUiState.isEntryValid,
onClick = onClickSave
) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.save_account_button), fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
if(tankId != 0L){
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = Color.Black
),
enabled = tankUiState.isEntryValid,
onClick = onClickDelete
) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.delete_account_button), fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
}
}
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(CustomYellow)
){
Column {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
){
Text(text="Добавить уровень", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Row(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier.fillMaxWidth()
){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(0.dp, 5.dp)
) {
Text(text="Уровень:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Column {
TextField(
value = "",
placeholder = { Text(text = "Уровень", color = CustomDark) },
onValueChange = { },
modifier = Modifier
.width(200.dp),
)
}
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 10.dp)
){
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = Color.Black
),
onClick = { }) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.create_account_button), fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
}
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(CustomYellow)
){
Column {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
){
Text(text="Добавить нацию", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Row(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier.fillMaxWidth()
){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(0.dp, 5.dp)
) {
Text(text="Нация:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold)
}
Column {
TextField(
value = "",
placeholder = { Text(text = "Нация", color = CustomDark) },
onValueChange = { },
modifier = Modifier
.width(200.dp),
)
}
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 10.dp)
){
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = Color.Black
),
onClick = onClickSave
) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.create_account_button), fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
}
}
}
Spacer(Modifier.height(20.dp))
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun ConstructorEditPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
Constructor(
tankId = 0L,
tankViewModel = viewModel(factory = AppViewModelProvider.Factory),
tankUiState = Tank.getTank().toUiState(true),
levelUiState = Level.DEMO_LEVEL.toUiState(),
levelsListUiState = LevelsListUiState(listOf()),
onLevelUpdate = { },
nationUiState = Nation.DEMO_NATION.toUiState(),
nationsListUiState = NationsListUiState(listOf()),
onNationUpdate = { },
onClickSave = { },
onClickDelete = { },
onUpdate = { },
)
}
}
}

View File

@ -0,0 +1,183 @@
package ru.ulstu.`is`.pmu.screen.composeui
import android.content.res.Configuration
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.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
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.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.ulstu.`is`.pmu.tank.composeui.list.TankListViewModel
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.screen.composeui.image.Dimensions
import ru.ulstu.`is`.pmu.screen.composeui.image.RoundedCorderImage
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomYellow
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun Hangar(
navController: NavController?,
viewModel: TankListViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
viewModel.setUserId(100L)
LaunchedEffect(Unit) {
viewModel.refreshUserTanksListState()
}
//новый вызов основного списка
Hangar(tankList = viewModel.usersTanksUiState.userTankList )
}
@Composable
private fun Hangar(
tankList: List<TankWithNationAndLevel>
) {
Column(
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
val startIndex = 0
if(tankList.isNotEmpty()){
val countRows = tankList.size / 2
var index = 0
var supportCountRow = countRows
//проверяем на то, что не всё поместилось в ряды по 2 элемента
if(tankList.size % 2 == 1){
supportCountRow++
}
for (n in 1..supportCountRow) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier
.fillMaxWidth()
.padding(5.dp, 0.dp, 5.dp, 0.dp)
) {
var supportSizeRow = 1
if(n == supportCountRow && index + supportSizeRow == tankList.size){
supportSizeRow = 0
}
//цикл для заполнения строки карточек
for (m in 0..supportSizeRow) {
Column(
modifier = Modifier.background(
color = CustomYellow
)
) {
Box(
Modifier
.background(CustomYellow, shape = RoundedCornerShape(Dimensions.cornerRadius))
.height(260.dp),
)
{
Card(
colors = CardDefaults.cardColors(
containerColor = CustomYellow,
),
modifier = Modifier
.size(width = 185.dp, height = 250.dp)
.padding(10.dp, 0.dp, 10.dp, 0.dp)
) {
RoundedCorderImage(
imageBitmap = tankList[index].image.asImageBitmap(),
modifier = Modifier.weight(0.5F)
)
Text(
text = tankList[index].name,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Нация: " + tankList[index].nationName,
fontSize = 17.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Уровень: " + tankList[index].level.toString(),
fontSize = 17.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Стоимость: " + tankList[index].price.toString(),
fontSize = 17.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
index++
}
}
}
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun HangarPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
Hangar(tankList = listOf())
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun HangarEmptyPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
Hangar(tankList = listOf())
}
}
}

View File

@ -0,0 +1,208 @@
package ru.ulstu.`is`.pmu.screen.composeui
import android.content.res.Configuration
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.composeui.navigation.Screen
import ru.ulstu.`is`.pmu.ui.theme.CustomBackground
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.CustomYellow
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@OptIn(ExperimentalMaterial3Api::class, ExperimentalTextApi::class)
@Composable
fun Login(navController: NavController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val currentScreen = currentDestination?.route?.let { Screen.getItem(it) }
//для букв с рамками
// Create a Paint that has black stroke
val textPaintStroke = Paint().asFrameworkPaint().apply {
isAntiAlias = true
style = android.graphics.Paint.Style.STROKE
textSize = 200f
color = android.graphics.Color.BLACK
strokeWidth = 40f
strokeMiter= 15f
strokeJoin = android.graphics.Paint.Join.ROUND
}
// Create a Paint that has white fill
val textPaint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
style = android.graphics.Paint.Style.FILL
textSize = 200f
color = CustomOrange.hashCode()
}
var userName by remember { mutableStateOf("") }
var userPassword by remember { mutableStateOf("") }
Box(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier.fillMaxSize()
){
Image(
painter = painterResource(id = R.drawable.login),
contentDescription = stringResource(id = R.string.tanks_main_title),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Column(
modifier = Modifier.fillMaxSize()
.padding(0.dp, 40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column(
verticalArrangement = Arrangement.spacedBy(70.dp)
){
Canvas(
modifier = Modifier.fillMaxWidth(),
onDraw = {
drawIntoCanvas {
it.nativeCanvas.drawText(
"TANKS",
240f,
80.dp.toPx(),
textPaintStroke
)
it.nativeCanvas.drawText(
"TANKS",
240f,
80.dp.toPx(),
textPaint
)
}
}
)
Canvas(
modifier = Modifier.fillMaxWidth(),
onDraw = {
drawIntoCanvas {
it.nativeCanvas.drawText(
"MOBILE",
200f,
80.dp.toPx(),
textPaintStroke
)
it.nativeCanvas.drawText(
"MOBILE",
200f,
80.dp.toPx(),
textPaint
)
}
}
)
}
Spacer(modifier = Modifier.weight(1.0f))
OutlinedTextField(
shape = RoundedCornerShape(50.dp),
value = userName,
placeholder = {Text(text = "Логин", color = CustomYellow)},
onValueChange = { userName = it },
modifier = Modifier
.width(200.dp)
.border(2.dp, CustomOrange, RoundedCornerShape(50.dp)),
colors = TextFieldDefaults.outlinedTextFieldColors(
containerColor = CustomBackground,
focusedBorderColor = CustomOrange,
unfocusedBorderColor = CustomOrange
),
)
Spacer(Modifier.height(10.dp))
OutlinedTextField(
shape = RoundedCornerShape(50.dp),
value = userPassword,
placeholder = {Text(text = "Пароль", color = CustomYellow)},
onValueChange = { userPassword = it },
modifier = Modifier
.width(200.dp)
.border(2.dp, CustomOrange, RoundedCornerShape(50.dp)),
colors = TextFieldDefaults.outlinedTextFieldColors(
containerColor = CustomBackground,
focusedBorderColor = CustomOrange,
unfocusedBorderColor = CustomOrange
),
)
Spacer(Modifier.height(50.dp))
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomYellow,
contentColor = CustomDark),
onClick = { navController.navigate(Screen.TankList.route) }) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.login_button), fontSize = 20.sp)
}
Spacer(Modifier.height(25.dp))
TextButton(
onClick={ navController.navigate(Screen.Register.route) }
) {
Text(
text = stringResource(id = R.string.register_button),
color = CustomOrange,
fontSize = 20.sp
)
}
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun LoginPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
val navController = rememberNavController()
Login(navController)
}
}
}

View File

@ -0,0 +1,98 @@
package ru.ulstu.`is`.pmu.screen.composeui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.lazy.LazyColumn
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.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import ru.ulstu.`is`.pmu.composeui.navigation.Screen
import ru.ulstu.`is`.pmu.tank.composeui.list.NationListViewModel
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.screen.composeui.image.Dimensions
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import androidx.compose.runtime.LaunchedEffect as LaunchedEffect
@Composable
fun NationList(
navController: NavController,
viewModel: NationListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val nations = viewModel.nationPagingFlow.flow.collectAsLazyPagingItems()
fun handleAddButtonClick() {
val route = Screen.getEntityRoute(Screen.Constructor, 0)
navController.navigate(route)
}
LaunchedEffect(Unit) {
viewModel.refresh()
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier.height(300.dp * nations.itemCount / 5),
) {
items(
count = nations.itemCount,
key = nations.itemKey { it.uid!! }
) {
val nation: Nation = nations[it] ?: return@items
NationBox(
nation = nation,
navController = navController
)
}
}
}
}
@Composable
fun NationBox(
nation: Nation,
navController: NavController
) {
fun handleOnClick() {
val route = Screen.getEntityRoute(Screen.EDIT_NATIONS, nation.uid!!)
navController.navigate(route)
}
Box(
modifier = Modifier
.fillMaxWidth()
.clickable(
onClick = {
handleOnClick()
}
)
.background(
MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(Dimensions.cornerRadius)
)
.padding(15.dp)
) {
Text(
text = nation.nationName,
color = MaterialTheme.colorScheme.onSurface
)
}
}

View File

@ -0,0 +1,142 @@
package ru.ulstu.`is`.pmu.screen.composeui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.composeui.navigation.Screen
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.CustomRed
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun Register(navController: NavController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val currentScreen = currentDestination?.route?.let { Screen.getItem(it) }
var userName by remember { mutableStateOf("") }
var userPassword by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.background(CustomDark)
.padding(0.dp, 70.dp)
){
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = stringResource(id = R.string.tanks_main_title),
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(0.dp, 40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.register_button),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
color = CustomOrange,
)
Spacer(modifier = Modifier.weight(0.2f))
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
){
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text="Логин:", fontSize = 30.sp, color = Color.White, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(10.dp))
Text(text="Пароль:", fontSize = 30.sp, color = Color.White, fontWeight = FontWeight.Bold)
}
Column {
TextField(
value = userName,
placeholder = { Text(text = "Логин", color = CustomDark) },
onValueChange = { userName = it },
modifier = Modifier
.width(200.dp),
)
Spacer(Modifier.height(10.dp))
TextField(
value = userPassword,
placeholder = { Text(text = "Пароль", color = CustomDark) },
onValueChange = { userPassword = it },
modifier = Modifier
.width(200.dp),
)
}
}
Spacer(Modifier.weight(1.0f))
Button(
modifier = Modifier
.width(200.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomRed,
contentColor = Color.White
),
onClick = { navController.navigate(Screen.Login.route) }) {
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.create_account_button), fontSize = 20.sp)
}
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun RegisterPreview() {
PmudemoTheme {
Surface(
) {
val navController = rememberNavController()
Register(navController)
}
}
}

View File

@ -0,0 +1,337 @@
package ru.ulstu.`is`.pmu.screen.composeui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.font.FontWeight
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.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.composeui.ApiStatus
import ru.ulstu.`is`.pmu.tank.composeui.edit.ReportViewModel
import ru.ulstu.`is`.pmu.tank.model.Report
import ru.ulstu.`is`.pmu.screen.composeui.image.RoundedCorderImage
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomRed
import ru.ulstu.`is`.pmu.ui.theme.CustomYellow
import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Report (
navController: NavController?,
viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
when (viewModel.apiStatus) {
ApiStatus.DONE -> {
val dateStateStart = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val dateStateEnd = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val coroutineScope = rememberCoroutineScope()
val reportResultPageState = viewModel.reportResultPageUiState
Column(
modifier = Modifier.background(CustomYellow),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(
text = "Начало периода",
style = MaterialTheme.typography.headlineLarge
)
DatePicker(
state = dateStateStart,
)
val selectedDateStart = dateStateStart.selectedDateMillis
if (selectedDateStart != null) {
val resultDate= Date(selectedDateStart)
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = resultDate))
}
else
{
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = Date(0)))
}
Text(
text = "Конец периода",
style = MaterialTheme.typography.headlineLarge
)
DatePicker(
state = dateStateEnd,
)
val selectedDateEnd = dateStateEnd.selectedDateMillis
if (selectedDateEnd != null) {
val resultDate = Date(selectedDateEnd)
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = resultDate))
}
else
{
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = Date(0)))
}
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {coroutineScope.launch { viewModel.getReport() } },
enabled = viewModel.reportPageUiState.isEntryValid,
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clip(RoundedCornerShape(8.dp)),
colors = ButtonDefaults.buttonColors(
CustomRed, Color.White, CustomYellow,
CustomYellow
),
) {
Text("Сформировать отчет")
}
Spacer(modifier = Modifier.height(32.dp))
Text(
text = "Результат",
style = MaterialTheme.typography.headlineLarge
)
if(reportResultPageState.resReport != null){
TableScreen(reportData = reportResultPageState.resReport!!)
}
}
}
ApiStatus.LOADING -> LoadingPlaceholder()
else -> ErrorPlaceholder(
message = viewModel.apiError,
onBack = {
navController?.popBackStack()
}
)
}
}
@Composable
fun LoadingPlaceholder() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = TextUnit(value = 25F, type = TextUnitType.Sp),
text = "Загрузка"
)
}
}
@Composable
fun ErrorPlaceholder(message: String, onBack: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = TextUnit(value = 20F, type = TextUnitType.Sp),
text = message,
color = Color(0xFFFF1744)
)
Spacer(modifier = Modifier.padding(bottom = 10.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onBack() }
) {
Text("Назад")
}
}
}
@Composable
fun TableScreen(reportData: Report) {
Column(
Modifier
.padding(16.dp)) {
Row(
Modifier
.background(CustomDark)
.padding(10.dp, 10.dp, 10.dp, 10.dp)) {
Card(
colors = CardDefaults.cardColors(
containerColor = CustomYellow,
),
modifier = Modifier
.padding(10.dp, 10.dp, 10.dp, 10.dp),
) {
Column {
Text(
text = "Самый популярный танк:",
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
RoundedCorderImage(
imageBitmap = reportData.miniature.asImageBitmap(),
modifier = Modifier.fillMaxWidth().fillMaxHeight()
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = reportData.tankName,
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Нация: " + reportData.tankNation,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Уровень: " + reportData.tankLevel.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Стоимость: " + reportData.price.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Покупок за период: " + reportData.countTankPurchase.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
Spacer(modifier = Modifier.height(10.dp))
Row(
Modifier
.background(CustomDark)
.padding(10.dp, 10.dp, 10.dp, 10.dp),
verticalAlignment = Alignment.CenterVertically
)
{
Card(
colors = CardDefaults.cardColors(
containerColor = CustomYellow,
),
modifier = Modifier
.fillMaxSize()
.padding(10.dp, 10.dp, 10.dp, 10.dp),
) {
Column(
modifier = Modifier.fillMaxHeight()
.padding(10.dp, 10.dp, 10.dp, 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Самый популярный уровень: " + reportData.level.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center
)
Text(
text = "Покупок за период: " + reportData.countLevelPurchase.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center
)
}
}
}
Spacer(modifier = Modifier.height(10.dp))
Row(
Modifier
.background(CustomDark)
.padding(10.dp, 10.dp, 10.dp, 10.dp),
) {
Card(
colors = CardDefaults.cardColors(
containerColor = CustomYellow,
),
modifier = Modifier
.fillMaxSize()
.padding(10.dp, 10.dp, 10.dp, 10.dp),
) {
Column(
modifier = Modifier.fillMaxHeight()
.padding(10.dp, 10.dp, 10.dp, 10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(
text = "Самая популярная нация: " + reportData.nationName,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center
)
Text(
text = "Покупок за период: " + reportData.countNationPurchase.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
textAlign = TextAlign.Center
)
}
}
}
}
}

View File

@ -0,0 +1,292 @@
package ru.ulstu.`is`.pmu.screen.composeui
import android.content.res.Configuration
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.composeui.navigation.Screen
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationDropDownViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationsListUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.PurchaseTankViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.UsersTanksEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.list.TankListViewModel
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.screen.composeui.image.CuteImage
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.CustomRed
import ru.ulstu.`is`.pmu.ui.theme.CustomYellow
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun TankList(
navController: NavController?,
userTankEditViewModel: UsersTanksEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
purchaseTankViewModel: PurchaseTankViewModel = viewModel(factory = AppViewModelProvider.Factory),
viewModel: TankListViewModel = viewModel(factory = AppViewModelProvider.Factory),
listNations: NationDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
LaunchedEffect(Unit) {
viewModel.refreshTankListState()
}
// Lazy Column, Pass the numbers array
if (navController != null) {
TankList(
purchaseTankViewModel = purchaseTankViewModel,
nations = listNations.nationsListUiState,
listTanks = viewModel.tankListUiState.tankList,
viewModel = userTankEditViewModel
) { uid: Long ->
val route = Screen.Constructor.route.replace("{id}", uid.toString())
navController.navigate(route)
}
}
}
@Composable
fun ColumnItem(
purchaseTankViewModel: PurchaseTankViewModel,
nation: Nation,
tanks: List<Tank>?,
onClick: (uid: Long) -> Unit,
viewModel: UsersTanksEditViewModel
) {
val scope = rememberCoroutineScope()
val tank = viewModel.usersTanksUiState
val userId = 100L
val coroutineScope = rememberCoroutineScope()
Column(
modifier = Modifier.padding(0.dp, 10.dp)
) {
Box(
Modifier
.background(CustomYellow)
.fillMaxWidth()
.height(40.dp),
){
Text(
text = nation.nationName,
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
color = CustomDark,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
Box(
Modifier
.background(CustomYellow)
.height(260.dp),
)
{
Row (
modifier = Modifier.horizontalScroll(ScrollState(0))
)
{
if(!tanks.isNullOrEmpty()){
tanks.forEach { tank ->
//TODO
if(tank.tankId != null){
key(tank.tankId) {
//val studentId = Screen.StudentView.route.replace("{id}", index.toString())
Card(
colors = CardDefaults.cardColors(
containerColor = CustomDark,
),
modifier = Modifier
.size(width = 200.dp, height = 250.dp)
.padding(all = 5.dp)
.clickable { onClick(tank.tankId) }
) {
CuteImage(
imageBitmap = tank.miniature.asImageBitmap(),
modifier = Modifier.fillMaxWidth()
)
Text(
text = tank.name,
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
color = CustomOrange,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 0.dp, 10.dp, 10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomRed,
contentColor = CustomDark),
onClick = {
coroutineScope.launch {
purchaseTankViewModel.savePurchase(tank.tankId, userId)
}
}
) {
Text(text = stringResource(id = R.string.purchase_button), color = Color.White)
}
}
}
}
}
} else {
Column(
modifier = Modifier.width(400.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
Text(
text = "Скоро!!!",
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
color = CustomDark,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
}
}
@Composable
private fun TankList(
purchaseTankViewModel: PurchaseTankViewModel,
nations: NationsListUiState,
listTanks: List<Tank>,
viewModel: UsersTanksEditViewModel,
onClick: (uid: Long) -> Unit,
) {
LazyColumn(
verticalArrangement = Arrangement.Bottom,
modifier = Modifier.height(300.dp * nations.nationList.size),
) {
// item places one item on the LazyScope
item {
//ColumnItem(number = 0)
}
// items(count) places number of items supplied
// as count and gives current count in the lazyItemScope
items(10) {currentCount->
//ColumnItem(number = currentCount)
}
// items(list/array) places number of items
// same as the size of list/array and gives
// current list/array item in the lazyItemScope
// тут как раз и выводим 3 раза все танки по нациям
// алгоритм разбиения танков по нациям
var dictNationTanks: Map<Nation, List<Tank>> = mutableMapOf()
var totalDictionary: Map<Nation, List<Tank>> = mutableMapOf()
var newListTanks: List<Tank>
nations.nationList.forEach { nation ->
newListTanks = mutableListOf()
listTanks.forEach{tank ->
if(tank.nationId == nation.uid){
newListTanks = newListTanks.plus(tank)
}
}
if(newListTanks.size > 0){
totalDictionary = dictNationTanks.plus(nation to newListTanks)
dictNationTanks = totalDictionary
}
}
items(nations.nationList) {nation ->
ColumnItem(purchaseTankViewModel = purchaseTankViewModel, nation = nation, tanks = totalDictionary[nation], onClick = onClick, viewModel = viewModel)
}
// items(list/array) places number of
// items same as the size of list/array
// and gives current list/array item and
// currentIndex in the lazyItemScope
itemsIndexed(nations.nationList) { index, item ->
//ColumnItem(number = index)
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun TankListPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
TankList(
purchaseTankViewModel = viewModel(factory = AppViewModelProvider.Factory),
nations = NationsListUiState(listOf()),
viewModel = viewModel(factory = AppViewModelProvider.Factory),
listTanks = (1..20).map { i -> Tank.getTank(i.toLong()) }
) { }
}
}
}
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun TankEmptyListPreview() {
PmudemoTheme {
Surface(
color = CustomDark
) {
TankList(
purchaseTankViewModel = viewModel(factory = AppViewModelProvider.Factory),
nations = NationsListUiState(listOf()),
viewModel = viewModel(factory = AppViewModelProvider.Factory),
listTanks = listOf()
) { }
}
}
}

View File

@ -0,0 +1,25 @@
package ru.ulstu.`is`.pmu.screen.composeui.image
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
@Composable
fun CuteImage(
imageBitmap: ImageBitmap,
modifier: Modifier = Modifier
) {
Image(
bitmap = imageBitmap,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = modifier
.aspectRatio(16F/12F)
.clip(RoundedCornerShape(topStart = Dimensions.cornerRadius, topEnd = Dimensions.cornerRadius))
)
}

View File

@ -0,0 +1,128 @@
package ru.ulstu.`is`.pmu.screen.composeui.image
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
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.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.ui.theme.CustomDark
import ru.ulstu.`is`.pmu.ui.theme.CustomOrange
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun CuteImageUploader(
bitmap: Bitmap?,
onResult: (Bitmap) -> Unit
) {
val context = LocalContext.current
val title: String = if (bitmap == null) {
stringResource(R.string.not_uploaded)
} else {
stringResource(R.string.size, bitmap.width, bitmap.height)
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri: Uri? ->
uri?.let {
val inputStream = context.contentResolver.openInputStream(uri)
val newBitmap: Bitmap = BitmapFactory.decodeStream(inputStream)
onResult(newBitmap)
}
}
)
fun handleUploadButtonClick() {
launcher.launch("image/*")
}
Row(
modifier = Modifier.height(IntrinsicSize.Min).padding(5.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(Dimensions.cornerRadius)
)
.weight(1F)
) {
if (bitmap != null) {
RoundedCorderImage(
imageBitmap = bitmap.asImageBitmap(),
modifier = Modifier.fillMaxSize()
)
} else {
Icon(
painter = painterResource(R.drawable.ic_camera),
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground
)
}
}
Spacer(modifier = Modifier.width(10.dp))
Column(
Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(Dimensions.cornerRadius)
)
.padding(10.dp).align(Alignment.CenterVertically)
) {
Text(
text = title,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(10.dp))
Button(
modifier = Modifier
.width(130.dp)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CustomOrange,
contentColor = CustomDark
),
onClick = { handleUploadButtonClick() },
){
Text(text = "Загрузить")
}
}
}
}
@Preview(name = "Light Mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun CuteImageLoaderPreview() {
PmudemoTheme {
CuteImageUploader(null) {}
}
}

View File

@ -0,0 +1,7 @@
package ru.ulstu.`is`.pmu.screen.composeui.image
import androidx.compose.ui.unit.dp
object Dimensions {
val cornerRadius = 10.dp
}

View File

@ -0,0 +1,25 @@
package ru.ulstu.`is`.pmu.screen.composeui.image
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
@Composable
fun RoundedCorderImage(
imageBitmap: ImageBitmap,
modifier: Modifier = Modifier
) {
Image(
bitmap = imageBitmap,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = modifier
.aspectRatio(16F/12F)
.clip(RoundedCornerShape(Dimensions.cornerRadius))
)
}

View File

@ -0,0 +1,11 @@
package ru.ulstu.`is`.pmu.tank.api
object ApiRoutes {
const val BASE = "http://10.0.2.2:8079/"
const val USER = "users"
const val LEVEL = "levels"
const val NATION = "nations"
const val TANK = "tanks"
const val USER_TANK = "users_tanks"
const val REPORT = "report"
}

View File

@ -0,0 +1,200 @@
package ru.ulstu.`is`.pmu.tank.api
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
import ru.ulstu.`is`.pmu.tank.api.model.LevelRemote
import ru.ulstu.`is`.pmu.tank.api.model.NationRemote
import ru.ulstu.`is`.pmu.tank.api.model.ReportRemote
import ru.ulstu.`is`.pmu.tank.api.model.TankRemote
import ru.ulstu.`is`.pmu.tank.api.model.TankWithNationAndLevelRemote
import ru.ulstu.`is`.pmu.tank.api.model.UserRemote
import ru.ulstu.`is`.pmu.tank.api.model.UserTankCrossRefRemote
import java.util.Date
interface ServerService {
// :[USER]
@GET("${ApiRoutes.USER}/{id}")
suspend fun getUser(
@Path("id") userId: Long
): UserRemote
@POST(ApiRoutes.USER)
suspend fun insertUser(
@Body user: UserRemote
): UserRemote
@PUT("${ApiRoutes.USER}/{id}")
suspend fun updateUser(
@Path("id") id: Long,
@Body user: UserRemote
): UserRemote
// ![USER]
// :[LEVEL]
@GET(ApiRoutes.LEVEL)
suspend fun getLevels(): List<LevelRemote>
@GET("${ApiRoutes.LEVEL}/{id}")
suspend fun getLevel(
@Path("id") id: Long
): LevelRemote
@POST(ApiRoutes.LEVEL)
suspend fun insertLevel(
@Body level: LevelRemote
): LevelRemote
@PUT("${ApiRoutes.LEVEL}/{id}")
suspend fun updateLevel(
@Path("id") id: Long,
@Body level: LevelRemote
): LevelRemote
@DELETE("${ApiRoutes.LEVEL}/{id}")
suspend fun deleteLevel(
@Path("id") id: Long
): LevelRemote
// ![LEVEL]
// :[NATION]
@GET(ApiRoutes.NATION)
suspend fun getNations(
@Query("_page") page: Int,
@Query("_limit") limit: Int
): List<NationRemote>
@GET(ApiRoutes.NATION)
suspend fun getAllNations(): List<NationRemote>
@GET("${ApiRoutes.NATION}/{id}")
suspend fun getNation(
@Path("id") id: Long
): NationRemote
@POST(ApiRoutes.NATION)
suspend fun insertNation(
@Body nation: NationRemote
): NationRemote
@PUT("${ApiRoutes.NATION}/{id}")
suspend fun updateNation(
@Path("id") id: Long,
@Body nation: NationRemote
): NationRemote
@DELETE("${ApiRoutes.NATION}/{id}")
suspend fun deleteNation(
@Path("id") id: Long
): NationRemote
// ![NATION]
// :[TANK]
@GET(ApiRoutes.TANK)
suspend fun getTanks(): List<TankRemote>
@GET(ApiRoutes.TANK)
suspend fun getTanks(
@Query("id") id: List<Long>
): List<TankRemote>
@GET("${ApiRoutes.TANK}/{id}")
suspend fun getTank(
@Path("id") id: Long
): TankRemote
@GET("${ApiRoutes.TANK}?_expand=users_tanks")
suspend fun getTanksForPurchase(
@Query("id") userId: Long
): List<TankRemote>
@GET("${ApiRoutes.TANK}?_embed=users_tanks")
suspend fun getTanksFromHangar(
@Query("id") id: Long
): List<TankWithNationAndLevelRemote>
@POST(ApiRoutes.TANK)
suspend fun insertTank(
@Body tank: TankRemote
): TankRemote
@PUT("${ApiRoutes.TANK}/{id}")
suspend fun updateTank(
@Path("id") id: Long,
@Body tank: TankRemote
): TankRemote
@DELETE("${ApiRoutes.TANK}/{id}")
suspend fun deleteTank(
@Path("id") id: Long
): TankRemote
@GET("${ApiRoutes.REPORT}")
suspend fun getReportInfo(
@Query("startDate") startDate: Date,
@Query("endDate") endDate: Date
): ReportRemote
// ![TANK]
// :[USER_TANK_CROSS_REF]
@GET("${ApiRoutes.USER_TANK}")
suspend fun getUserTankCrossRef(
@Query("userId") userId: Long
): List<UserTankCrossRefRemote>
@POST("${ApiRoutes.USER_TANK}")
suspend fun insertUserTankCrossRef(
@Body users_tank: UserTankCrossRefRemote
) : UserTankCrossRefRemote
@DELETE("${ApiRoutes.USER_TANK}/deleteMyTank")
suspend fun deleteUserTankCrossRef(
@Body tank: UserTankCrossRefRemote
): UserTankCrossRefRemote
// ![USER_TANK_CROSS_REF]
companion object {
private const val BASE_URL = ApiRoutes.BASE
@Volatile
private var INSTANCE: ServerService? = null
fun getInstance(): ServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(ServerService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,109 @@
package ru.ulstu.`is`.pmu.tank.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import retrofit2.HttpException
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.toNation
import ru.ulstu.`is`.pmu.tank.database.AppDatabase
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.RemoteKeyType
import ru.ulstu.`is`.pmu.tank.model.RemoteKeys
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineRemoteKeyRepository
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class NationRemoteMediator(
private val service: ServerService,
private val dbNationRepository: OfflineNationRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Nation>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Nation>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val nations = service.getNations(page, state.config.pageSize).map { it.toNation() }
val endOfPaginationReached = nations.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.NATION)
dbNationRepository.deleteNations()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = nations.map {
RemoteKeys(
entityId = it.uid!!.toInt(),
type = RemoteKeyType.NATION,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbNationRepository.insertNations(nations)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Nation>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { nation ->
dbRemoteKeyRepository.getAllRemoteKeys(nation.uid!!.toInt(), RemoteKeyType.NATION)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Nation>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { nation ->
dbRemoteKeyRepository.getAllRemoteKeys(nation.uid!!.toInt(), RemoteKeyType.NATION)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Nation>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { nationUid ->
dbRemoteKeyRepository.getAllRemoteKeys(nationUid.toInt(), RemoteKeyType.NATION)
}
}
}
}

View File

@ -0,0 +1,109 @@
package ru.ulstu.`is`.pmu.tank.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import retrofit2.HttpException
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.toTank
import ru.ulstu.`is`.pmu.tank.database.AppDatabase
import ru.ulstu.`is`.pmu.tank.model.RemoteKeyType
import ru.ulstu.`is`.pmu.tank.model.RemoteKeys
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineRemoteKeyRepository
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineTankRepository
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class TankRemoteMediator(
private val service: ServerService,
private val dbTankRepository: OfflineTankRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Tank>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Tank>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val tanks = service.getTanks().map { it.toTank() }
val endOfPaginationReached = tanks.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.NATION)
dbTankRepository.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = tanks.map {
RemoteKeys(
entityId = it.tankId!!.toInt(),
type = RemoteKeyType.NATION,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbTankRepository.insertMany(tanks)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Tank>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { tank ->
dbRemoteKeyRepository.getAllRemoteKeys(tank.tankId!!.toInt(), RemoteKeyType.NATION)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Tank>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { tank ->
dbRemoteKeyRepository.getAllRemoteKeys(tank.tankId!!.toInt(), RemoteKeyType.NATION)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Tank>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.tankId?.let { tankUid ->
dbRemoteKeyRepository.getAllRemoteKeys(tankUid.toInt(), RemoteKeyType.NATION)
}
}
}
}

View File

@ -0,0 +1,22 @@
package ru.ulstu.`is`.pmu.tank.api.model
import androidx.room.ColumnInfo
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Nation
@Serializable
data class LevelRemote (
val id: Long = 0,
val level: Int = 0
)
fun LevelRemote.toLevel(): Level = Level(
uid = id,
level
)
fun Level.toRemote(): LevelRemote = LevelRemote(
id = uid!!,
level
)

View File

@ -0,0 +1,20 @@
package ru.ulstu.`is`.pmu.tank.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.Nation
@Serializable
data class NationRemote(
val id: Long = 0,
val nationName: String = ""
)
fun NationRemote.toNation(): Nation = Nation(
uid = id,
nationName
)
fun Nation.toRemote(): NationRemote = NationRemote(
id = uid!!,
nationName
)

View File

@ -0,0 +1,42 @@
package ru.ulstu.`is`.pmu.tank.api.model
import com.application.ui.toBitmap
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.Report
import ru.ulstu.`is`.pmu.tank.model.Tank
@Serializable
data class ReportRemote(
//tank part
val id: Int = 0,
val tankName: String = "",
val price: Int = 0,
val miniature: String = "",
val tankLevel: Int = 0,
val tankNation: String = "",
val countTankPurchase: Int = 0,
//level part
val level: Int = 0,
val countLevelPurchase: Int = 0,
//nation part
val nationName: String = "",
val countNationPurchase: Int = 0,
){ }
fun ReportRemote.toReport(): Report = Report(
id = id,
tankName = tankName,
price = price,
miniature = miniature.toBitmap(),
tankLevel = tankLevel,
tankNation = tankNation,
countTankPurchase = countTankPurchase,
level = level,
countLevelPurchase = countLevelPurchase,
nationName = nationName,
countNationPurchase = countNationPurchase
)

View File

@ -0,0 +1,35 @@
package ru.ulstu.`is`.pmu.tank.api.model
import android.graphics.Bitmap
import com.application.ui.toBase64
import com.application.ui.toBitmap
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.Tank
@Serializable
data class TankRemote(
val id: Long = 0,
val name: String = "",
val price: Int = 0,
val miniature: String = "",
val levelId: Long = 0,
val nationId: Long = 0,
)
fun TankRemote.toTank(): Tank = Tank(
tankId = id,
name = name,
price = price,
miniature = miniature.toBitmap(),
levelId = levelId,
nationId = nationId
)
fun Tank.toRemote(): TankRemote = TankRemote(
id = tankId!!,
name = name,
price = price,
miniature = miniature.toBase64(),
levelId = levelId!!,
nationId = nationId!!
)

View File

@ -0,0 +1,34 @@
package ru.ulstu.`is`.pmu.tank.api.model
import com.application.ui.toBase64
import com.application.ui.toBitmap
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
@Serializable
data class TankWithNationAndLevelRemote(
val id: Long,
val name: String,
val price: Int,
val image: String,
val level: Int,
val nationName: String
)
fun TankWithNationAndLevelRemote.toTankWithNationAndLevel(): TankWithNationAndLevel = TankWithNationAndLevel(
tankId = id,
name = name,
price = price,
image = image.toBitmap(),
level = level,
nationName = nationName
)
fun TankWithNationAndLevel.toRemote(): TankWithNationAndLevelRemote = TankWithNationAndLevelRemote(
id = tankId!!,
name = name,
price = price,
image = image.toBase64(),
level = level,
nationName = nationName
)

View File

@ -0,0 +1,33 @@
package ru.ulstu.`is`.pmu.tank.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserRole
@Serializable
data class UserRemote (
val id: Long = 0,
val nickname: String = "",
val email: String = "",
val password: String = "",
val role: Int = 0,
val balance: Int = 0
)
fun UserRemote.toUser(): User = User(
userId = id,
nickname = nickname,
email = email,
password = password,
role = enumValues<UserRole>()[role],
balance = balance
)
fun User.toRemote(): UserRemote = UserRemote(
id = userId,
nickname = nickname,
email = email,
password = password,
role = role.ordinal,
balance = balance
)

View File

@ -0,0 +1,29 @@
package ru.ulstu.`is`.pmu.tank.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.database.DateSerializer
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import java.util.Date
@Serializable
data class UserTankCrossRefRemote (
val id: Long? = 0,
val userId: Long = 0,
val tankId: Long = 0,
@Serializable(with = DateSerializer::class)
val date: Date = Date(0)
)
fun UserTankCrossRefRemote.toUserTankCrossRef(): UserTankCrossRef = UserTankCrossRef(
id = id,
userId = userId,
tankId = tankId,
date = date
)
fun UserTankCrossRef.toRemote(): UserTankCrossRefRemote = UserTankCrossRefRemote(
userId = userId,
tankId = tankId,
date = date
)

View File

@ -0,0 +1,51 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.toLevel
import ru.ulstu.`is`.pmu.tank.api.model.toRemote
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.LevelWithTanks
import ru.ulstu.`is`.pmu.tank.repository.LevelRepository
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineLevelRepository
class RestLevelRepository(
private val service: ServerService,
private val dbLevelRepository: OfflineLevelRepository,
) : LevelRepository {
override suspend fun getAllLevels(): List<Level> {
dbLevelRepository.deleteAll()
val levels = service.getLevels().map { it.toLevel() }
dbLevelRepository.insertMany(levels)
return levels
}
override suspend fun getSimpleLevel(uid: Long): Level {
TODO("Not yet implemented")
}
override fun getFullLevel(uid: Long): Flow<LevelWithTanks?> {
TODO("Not yet implemented")
}
override suspend fun insertLevel(level: Level) {
service.insertLevel(level.toRemote())
}
override suspend fun insertMany(levels: List<Level>) {
TODO("Not yet implemented")
}
override suspend fun updateLevel(level: Level) {
service.updateLevel(level.uid!!, level.toRemote())
}
override suspend fun deleteLevel(level: Level) {
service.deleteLevel(level.uid!!)
dbLevelRepository.deleteLevel(level)
}
override suspend fun deleteAll() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,83 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.mediator.NationRemoteMediator
import ru.ulstu.`is`.pmu.tank.api.model.toNation
import ru.ulstu.`is`.pmu.tank.api.model.toRemote
import ru.ulstu.`is`.pmu.tank.database.AppContainer
import ru.ulstu.`is`.pmu.tank.database.AppDatabase
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.NationWithTanks
import ru.ulstu.`is`.pmu.tank.repository.NationRepository
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineRemoteKeyRepository
class RestNationRepository(
private val service: ServerService,
private val dbNationRepository: OfflineNationRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : NationRepository {
override suspend fun getAllNations(): List<Nation> {
dbNationRepository.deleteNations()
val nations = service.getAllNations().map { it.toNation() }
dbNationRepository.insertNations(nations)
return nations
}
override fun getAll(): Flow<PagingData<Nation>> {
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = NationRemoteMediator(
service = service,
dbNationRepository = dbNationRepository,
dbRemoteKeyRepository = dbRemoteKeyRepository,
database = database,
),
) {
dbNationRepository.pagingSource()
}.flow
}
override fun getSimpleNation(uid: Long): Flow<Nation?> {
TODO("Not yet implemented")
}
override fun getFullNation(uid: Long): Flow<NationWithTanks?> {
TODO("Not yet implemented")
}
override fun pagingSource(): PagingSource<Int, Nation> {
TODO("Not yet implemented")
}
override suspend fun insertNation(nation: Nation) {
service.insertNation(nation.toRemote()).toNation()
}
override suspend fun insertNations(nations: List<Nation>) {
TODO("Not yet implemented")
}
override suspend fun updateNation(nation: Nation) {
service.updateNation(nation.uid!!, nation.toRemote())
}
override suspend fun deleteNation(nation: Nation) {
service.deleteNation(nation.uid!!)
}
override suspend fun deleteNations() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,133 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import android.graphics.Bitmap
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.ReportRemote
import ru.ulstu.`is`.pmu.tank.api.model.UserTankCrossRefRemote
import ru.ulstu.`is`.pmu.tank.api.model.toLevel
import ru.ulstu.`is`.pmu.tank.api.model.toNation
import ru.ulstu.`is`.pmu.tank.api.model.toRemote
import ru.ulstu.`is`.pmu.tank.api.model.toReport
import ru.ulstu.`is`.pmu.tank.api.model.toTank
import ru.ulstu.`is`.pmu.tank.api.model.toTankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.api.model.toUserTankCrossRef
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.Report
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineTankRepository
import java.nio.file.Files.find
import java.util.Date
class RestTankRepository (
private val service: ServerService,
private val dbTankRepository: OfflineTankRepository
) : TankRepository {
override suspend fun getAll(): List<Tank> {
return service.getTanks().map { it.toTank() }
}
override suspend fun getForUserAll(userId: Long): List<Tank> {
val totalList: List<Tank> = getAll()
val totalUserTankList: List<UserTankCrossRef> = service.getUserTankCrossRef(100L).map{ it.toUserTankCrossRef() }
//спискок, который вернём
var supportList = ArrayList<Tank>()
//вспомогательный список для проверки отсутствия танка у пользователя
var notToUser: List<UserTankCrossRef> = totalList.map{ UserTankCrossRef(userId, it.tankId!!, Date()) }
if (totalUserTankList.isEmpty()){
return totalList
} else {
notToUser.forEach(){userTank ->
if(!totalUserTankList.contains(userTank)){
supportList.add(totalList.firstOrNull { it.tankId == userTank.tankId }!!)
}
}
return supportList
}
}
//очень классная штука
@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun <T> Flow<List<T>>.flattenToList() =
flatMapConcat { it.asFlow() }.toList()
override suspend fun getTank(uid: Long): Tank {
return service.getTank(uid).toTank()
}
suspend fun getReport(startDate: Date, endDate: Date): Report {
return service.getReportInfo(startDate, endDate).toReport();
}
override suspend fun getUserTanks(userId: Long): List<TankWithNationAndLevel> {
val totalList: List<Tank> = getAll()
val totalLevelList: List<Level> = service.getLevels().map { it.toLevel() }
val totalNationList: List<Nation> = service.getAllNations().map { it.toNation() }
//все имеющиеся танки у пользователя
val totalUserTankList: List<UserTankCrossRef> = service.getUserTankCrossRef(100L).map{ it.toUserTankCrossRef() }
//спискок, который вернём
var supportList = ArrayList<TankWithNationAndLevel>()
//список id-шников танков пользователя
var idList = ArrayList<Long>()
totalUserTankList.forEach(){ tank ->
idList.add(tank.tankId)
}
//список танков пользователя
var simpleTankList = service.getTanks(idList)
if (totalUserTankList.isEmpty()){
return listOf()
} else {
simpleTankList.forEach(){userTank ->
val tank = totalList.firstOrNull { it.tankId == userTank.id }!!
supportList.add(
TankWithNationAndLevel(tank.tankId, tank.name, tank.price, tank.miniature,
totalLevelList.first { it.uid == tank.levelId }.level,
totalNationList.first { it.uid == tank.nationId }.nationName)
)
}
return supportList
}
}
override suspend fun insertTank(tank: Tank, image: Bitmap) {
service.insertTank(tank.toRemote())
}
override suspend fun buyTank(tankId: Long, userId: Long, date: Date) {
service.insertUserTankCrossRef(UserTankCrossRef(userId, tankId, date).toRemote())
}
override suspend fun insertMany(tankList: List<Tank>) {
TODO("Not yet implemented")
}
override suspend fun updateTank(tank: Tank, image: Bitmap) {
service.updateTank(tank.tankId!!, tank.toRemote())
}
override suspend fun deleteTank(tank: Tank) {
service.deleteTank(tank.tankId!!)
}
override suspend fun deleteAll() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,40 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.toRemote
import ru.ulstu.`is`.pmu.tank.api.model.toUser
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.repository.offline.OfflineUserRepository
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
class RestUserRepository(
private val service: ServerService,
private val dbUserRepository: OfflineUserRepository,
) : UserRepository {
override fun getAllUsers(): Flow<List<User>> {
TODO("Not yet implemented")
}
override suspend fun getSimpleUser(userId: Long): User {
return service.getUser(userId).toUser()
}
override fun getFullUser(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>> {
TODO("Not yet implemented")
}
override suspend fun insertUser(user: User) {
service.insertUser(user.toRemote())
dbUserRepository.insertUser(user)
}
override suspend fun updateUser(user: User) {
service.updateUser(user.userId, user.toRemote())
}
override suspend fun deleteUser(user: User) {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,3 @@
package ru.ulstu.`is`.pmu.tank.composeui
enum class ApiStatus { LOADING, ERROR, DONE }

View File

@ -0,0 +1,47 @@
package ru.ulstu.`is`.pmu.tank.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
open class MyViewModel : ViewModel() {
var apiStatus by mutableStateOf(ApiStatus.DONE)
private set
var apiError by mutableStateOf("")
private set
fun runInScope(
actionSuccess: suspend () -> Unit,
actionError: suspend () -> Unit
) {
viewModelScope.launch {
apiStatus = ApiStatus.LOADING
runCatching {
actionSuccess()
apiStatus = ApiStatus.DONE
apiError = ""
}.onFailure { e: Throwable ->
when (e) {
is IOException,
is HttpException -> {
actionError()
apiStatus = ApiStatus.ERROR
apiError = e.localizedMessage ?: e.toString()
}
else -> throw e
}
}
}
}
fun runInScope(actionSuccess: suspend () -> Unit) {
runInScope(actionSuccess, actionError = {})
}
}

View File

@ -0,0 +1,46 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.repository.LevelRepository
class LevelDropDownViewModel (
private val levelRepository: LevelRepository
) : ViewModel() {
var levelsListUiState by mutableStateOf(LevelsListUiState())
private set
var levelUiState by mutableStateOf(LevelUiState())
private set
init {
viewModelScope.launch {
levelsListUiState = LevelsListUiState(levelRepository.getAllLevels())
}
}
fun setCurrentLevel(levelId: Long) {
val level: Level? =
levelsListUiState.levelList.firstOrNull { level -> level.uid == levelId }
level?.let { updateUiState(it) }
}
fun updateUiState(level: Level) {
levelUiState = LevelUiState(
level = level
)
}
}
data class LevelsListUiState(val levelList: List<Level> = listOf())
data class LevelUiState(
val level: Level? = null
)
fun Level.toUiState() = LevelUiState(level = Level(uid = uid, level = level))

View File

@ -0,0 +1,46 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.repository.NationRepository
class NationDropDownViewModel(
private val nationRepository: NationRepository
) : ViewModel() {
var nationsListUiState by mutableStateOf(NationsListUiState())
private set
var nationUiState by mutableStateOf(NationUiState())
private set
init {
viewModelScope.launch {
nationsListUiState = NationsListUiState(nationRepository.getAllNations())
}
}
fun setCurrentNation(nationId: Long) {
val nation: Nation? =
nationsListUiState.nationList.firstOrNull { nation -> nation.uid == nationId }
nation?.let { updateUiState(it) }
}
fun updateUiState(nation: Nation) {
nationUiState = NationUiState(
nation = nation
)
}
}
data class NationsListUiState(val nationList: List<Nation> = listOf())
data class NationUiState(
val nation: Nation? = null
)
fun Nation.toUiState() = NationUiState(nation = Nation(uid = uid, nationName = nationName))

View File

@ -0,0 +1,22 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import java.util.Calendar
import java.util.Date
class PurchaseTankViewModel (
savedStateHandle: SavedStateHandle,
private val tankRepository: TankRepository
) : ViewModel() {
suspend fun savePurchase(tankId: Long, userId: Long){
if(tankId > 0 && userId > 0){
tankRepository.buyTank(tankId, userId, Calendar.getInstance().time)
}
}
}

View File

@ -0,0 +1,65 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import ru.ulstu.`is`.pmu.tank.api.model.ReportRemote
import ru.ulstu.`is`.pmu.tank.api.repository.RestTankRepository
import ru.ulstu.`is`.pmu.tank.composeui.MyViewModel
import ru.ulstu.`is`.pmu.tank.model.Report
import java.util.Date
class ReportViewModel (
private val tankRepository: RestTankRepository
): MyViewModel()
{
var reportPageUiState by mutableStateOf(ReportPageUiState())
private set
var reportResultPageUiState by mutableStateOf(ReportResultPageUiState())
private set
fun onUpdate(reportDetails: ReportDetails)
{
reportPageUiState = ReportPageUiState(reportDetails = reportDetails,isEntryValid = validateInput(reportDetails))
}
private fun validateInput(uiState: ReportDetails = reportPageUiState.reportDetails): Boolean {
Log.d("Checking",uiState.endDate.toString())
return with(uiState) {
startDate!=Date(0)
&& endDate!=Date(0)
&& startDate < endDate
}
}
suspend fun getReport(){
runInScope(
actionSuccess = {
val res = tankRepository.getReport(reportPageUiState.reportDetails.startDate, reportPageUiState.reportDetails.endDate)
Log.d("MAIN CHECKING", res.toString())
reportResultPageUiState = ReportResultPageUiState(res)
},
actionError = {
reportResultPageUiState = ReportResultPageUiState()
}
)
}
}
data class ReportDetails(
val startDate: Date = Date(0),
val endDate: Date = Date(0)
)
data class ReportPageUiState(
val reportDetails: ReportDetails = ReportDetails(),
val isEntryValid: Boolean = false
)
data class ReportResultPageUiState(
var resReport: Report? = null
)

View File

@ -0,0 +1,110 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import android.graphics.Bitmap
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.application.ui.getEmptyBitmap
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
class TankEditViewModel(
savedStateHandle: SavedStateHandle,
private val tankRepository: TankRepository
) : ViewModel() {
var tankUiState by mutableStateOf(TankUiState())
private set
private val tankUid: Long = checkNotNull(savedStateHandle["id"])
init {
Log.d("hi-hi", tankUid.toString())
viewModelScope.launch {
if (tankUid > 0) {
tankUiState = tankRepository.getTank(tankUid)
.toUiState(true)
}
}
}
fun getTankId(): Long{
return tankUid;
}
fun updateUiState(tankDetails: TankDetails) {
tankUiState = TankUiState(
tankDetails = tankDetails,
isEntryValid = validateInput(tankDetails)
)
}
suspend fun saveTank() {
if (validateInput()) {
val image: Bitmap = tankUiState.tankDetails.miniature!!
if (tankUid > 0) {
tankRepository.updateTank(tankUiState.tankDetails.toTank(tankUid), image = image)
} else {
tankRepository.insertTank(tankUiState.tankDetails.toTank(), image = image)
}
}
}
suspend fun deleteTank() {
if (validateInput()) {
if (tankUid > 0) {
tankRepository.deleteTank(tankUiState.tankDetails.toTank(tankUid))
}
}
}
private fun validateInput(uiState: TankDetails = tankUiState.tankDetails): Boolean {
return with(uiState) {
name.isNotBlank()
&& price > 0
&& levelId!! != 0L
&& nationId!! != 0L
&& miniature != null
}
}
}
data class TankUiState(
val tankDetails: TankDetails = TankDetails(),
val isEntryValid: Boolean = false
)
data class TankDetails(
val name: String = "",
val price: Int = 0,
val levelId: Long? = 0,
val nationId: Long? = 0,
val miniature: Bitmap = getEmptyBitmap(),
val imageId: Long = -1
)
fun TankDetails.toTank(uid: Long = 0): Tank = Tank(
tankId = uid,
name = name,
price = price,
levelId = levelId,
nationId = nationId,
miniature = miniature,
)
fun Tank.toDetails(): TankDetails = TankDetails(
name = name,
price = price,
levelId = levelId,
nationId = nationId,
miniature = miniature,
)
fun Tank.toUiState(isEntryValid: Boolean = false): TankUiState = TankUiState(
tankDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,97 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserRole
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
class UserEditViewModel(
savedStateHandle: SavedStateHandle,
private val userRepository: UserRepository
) : ViewModel() {
var userUiState by mutableStateOf(UserUiState())
private set
private val userId: Long = 100L
init {
viewModelScope.launch {
if (userId > 0) {
userUiState = userRepository.getSimpleUser(userId)
.toUiState(true)
}
}
}
fun updateUiState(userDetails: UserDetails) {
userUiState = UserUiState(
userDetails = userDetails,
isEntryValid = validateInput(userDetails)
)
}
suspend fun saveUser() {
if (validateInput()) {
val user: User = userUiState.userDetails.toUser()
if (user.userId != 0.toLong()) {
userRepository.updateUser(user)
} else {
userRepository.insertUser(user)
}
}
}
private fun validateInput(uiState: UserDetails = userUiState.userDetails): Boolean {
return with(uiState) {
nickname.isNotBlank()
&& email.isNotBlank()
&& password.isNotBlank()
&& role.value > -1
&& balance > 0
}
}
}
data class UserUiState(
val userDetails: UserDetails = UserDetails(),
val isEntryValid: Boolean = false
)
data class UserDetails(
val nickname: String = "",
val email: String = "",
val password: String = "",
val role: UserRole = UserRole.USER,
val balance: Int = 0,
)
fun UserDetails.toUser(uid: Long = 0): User = User(
userId = uid,
nickname = nickname,
email = email,
password = password,
role = role,
balance = balance
)
fun User.toDetails(): UserDetails = UserDetails(
nickname = nickname,
email = email,
password = password,
role = role,
balance = balance
)
fun User.toUiState(isEntryValid: Boolean = false): UserUiState = UserUiState(
userDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,25 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
class UsersTanksEditViewModel(
savedStateHandle: SavedStateHandle,
private val usersTanksRepository: UsersTanksRepository
) : ViewModel() {
var usersTanksUiState by mutableStateOf(UserTankCrossRefUiState())
private set
suspend fun saveUserTank() {
//usersTanksRepository.insertUserTank(UserTankCrossRef(userId, tankId))
}
}
data class UserTankCrossRefUiState(
val userTankCrossRef: UserTankCrossRef = UserTankCrossRef.getEmpty(),
)

View File

@ -0,0 +1,31 @@
package ru.ulstu.`is`.pmu.tank.composeui.list
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.repository.NationRepository
class NationListViewModel(
private val nationRepository: NationRepository
) : ViewModel() {
var nationPagingFlow by mutableStateOf(NationPagingFlowState())
private set
fun refresh() {
val pagingSource = nationRepository.getAll()
nationPagingFlow = NationPagingFlowState(pagingSource.cachedIn(viewModelScope))
}
}
data class NationPagingFlowState(
val flow: Flow<PagingData<Nation>> = emptyFlow(),
)

View File

@ -0,0 +1,54 @@
package ru.ulstu.`is`.pmu.tank.composeui.list
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.database.AppDataContainer
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
class TankListViewModel(
private val tankRepository: TankRepository,
private var userId: Long = 100L
) : ViewModel() {
var tankListUiState by mutableStateOf(TankListUiState())
private set
var usersTanksUiState by mutableStateOf(UserTankListUiState())
private set
suspend fun deleteTank(tank: Tank) {
tankRepository.deleteTank(tank)
}
fun setUserId(uid: Long){
userId = uid
}
fun refreshTankListState() {
viewModelScope.launch {
tankListUiState = TankListUiState(tankRepository.getForUserAll(userId))
}
}
suspend fun refreshUserTanksListState(){
viewModelScope.launch {
usersTanksUiState = UserTankListUiState(tankRepository.getUserTanks(userId))
}
}
}
data class TankListUiState(val tankList: List<Tank> = listOf())
data class UserTankListUiState(val userTankList: List<TankWithNationAndLevel> = listOf())

View File

@ -0,0 +1,49 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.LevelWithTanks
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.User
@Dao
interface LevelDao {
@Query("select * from levels")
suspend fun getAll(): List<Level>
//получить уровни с танками
@Transaction
@Query("SELECT * FROM levels")
suspend fun getLevelsWithTanks(): List<LevelWithTanks>
//получить конкретный уровень
@Query("select * from levels where levels.uid = :uid")
fun getLevelUid(uid: Long): Flow<LevelWithTanks?>
//получить уровень без списка танков
@Query(
"SELECT level FROM levels where levels.uid = :uid"
)
open fun getSimpleLevelUid(uid: Long): Level
@Insert
suspend fun insert(level: Level)
@Insert
suspend fun insertMany(level: List<Level>)
@Update
suspend fun update(level: Level)
@Delete
suspend fun delete(level: Level)
@Query("DELETE FROM levels")
suspend fun deleteAll()
}

View File

@ -0,0 +1,55 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import ru.ulstu.`is`.pmu.tank.model.Nation
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.NationWithTanks
@Dao
interface NationDao {
@Query("select * from nations")
suspend fun getAll(): List<Nation>
//получить нации с танками
@Transaction
@Query("SELECT * FROM nations")
suspend fun getNationsWithTanks(): List<NationWithTanks>
//получить конкретную нацию
@Transaction
@Query("select * from nations where nations.uid = :uid")
fun getNationUid(uid: Long): Flow<NationWithTanks?>
//получить нацию без списка танков
@Transaction
@Query(
"SELECT nationName FROM nations where nations.uid = :uid"
)
open fun getSimpleNationUid(uid: Long): Flow<Nation?>
@Transaction
@Query("SELECT * FROM nations")
fun pagingSource(): PagingSource<Int, Nation>
@Insert
suspend fun insert(nation: Nation)
@Insert
suspend fun insertMany(nations: List<Nation>)
@Update
suspend fun update(nation: Nation)
@Delete
suspend fun delete(nation: Nation)
@Query("DELETE FROM nations")
suspend fun deleteAll()
}

View File

@ -0,0 +1,20 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import ru.ulstu.`is`.pmu.tank.model.RemoteKeyType
import ru.ulstu.`is`.pmu.tank.model.RemoteKeys
@Dao
interface RemoteKeysDao {
@Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type")
suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -0,0 +1,50 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
@Dao
interface TankDao {
@Query("select * from tanks GROUP BY nationId, levelId ORDER BY nationId")
fun getAll(): List<Tank>
//получить конкретный танк
@Query("select t.* from tanks AS t " +
"where t.tankId = :uid")
fun getTankUid(uid: Long): Tank
//получаем все танки пользователя по его Id
@Query(
"SELECT t.tankId, t.name, t.price, t.miniature AS image, l.level, n.nationName FROM users_tanks AS ut " +
"LEFT JOIN tanks as t on ut.tankId = t.tankId " +
"LEFT JOIN levels as l on t.levelId = l.uid " +
"LEFT JOIN nations as n on t.nationId = n.uid " +
"WHERE ut.userId = :uid GROUP BY t.nationId, t.levelId ORDER BY t.nationId"
)
fun getUserTanks(uid: Long): List<TankWithNationAndLevel>
@Query(
"SELECT t.* FROM tanks AS t WHERE t.tankId NOT IN (SELECT ut.tankId FROM users_tanks AS ut WHERE ut.userId = :uid)"
)
fun getNotUserTank(uid: Long): List<Tank>
@Insert
suspend fun insert(tank: Tank)
@Insert
suspend fun insertMany(tankList: List<Tank>)
@Update
suspend fun update(tank: Tank)
@Delete
suspend fun delete(tank: Tank)
@Query("DELETE FROM tanks")
suspend fun deleteAll()
}

View File

@ -0,0 +1,26 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import ru.ulstu.`is`.pmu.tank.model.TankImage
@Dao
interface TankImageDao {
@Insert
suspend fun insert(tankImage: TankImage): Long
@Insert
suspend fun insertMany(tankImages: List<TankImage>)
@Update
suspend fun update(tankImage: TankImage)
@Delete
suspend fun delete(tankImage: TankImage)
@Query("DELETE FROM tank_images WHERE image_id = :id")
suspend fun delete(id: Long)
}

View File

@ -0,0 +1,47 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
@Dao
interface UserDao {
@Query("select * from users order by nickname collate nocase asc")
fun getAll(): Flow<List<User>>
//получить конкретного пользователя
@Query(
"SELECT u.*, t.*, l.level, n.nationName, ti.data AS image FROM users AS u " +
"LEFT JOIN users_tanks as ut on u.userId = ut.userId " +
"LEFT JOIN tanks as t on ut.tankId = t.tankId " +
"LEFT JOIN tank_images as ti on ut.tankId = ti.image_id " +
"LEFT JOIN levels as l on t.levelId = l.uid " +
"LEFT JOIN nations as n on t.nationId = n.uid " +
"WHERE u.userId = :uid"
)
fun getUserUid(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>>
@Query("select * from users where users.userId = :userId")
fun getSimpleUserUid(userId: Long): User
//добавить танк в ангар пользователя
@Insert
suspend fun insert(userTankCrossRef: UserTankCrossRef) : Long
@Insert
suspend fun insert(user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
}

View File

@ -0,0 +1,25 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
@Dao
interface UsersTanksDao {
@Query("select * from users_tanks GROUP BY userId")
fun getAll(): List<UserTankCrossRef>
@Query("select * from users_tanks WHERE userId = :userId GROUP BY userId")
suspend fun getAllById(userId: Long): List<UserTankCrossRef>
@Insert
suspend fun insert(userTankCrossRef: UserTankCrossRef)
@Insert
suspend fun insertMany(userTankCrossRefList: List<UserTankCrossRef>)
@Query("DELETE FROM users_tanks")
suspend fun deleteAll()
}

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