Compare commits

...

44 Commits

Author SHA1 Message Date
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
179 changed files with 8304 additions and 0 deletions

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,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="ModuleClassLoaderOverlays">
<paths>
<option value="C:\Users\egore\AppData\Local\Temp\overlay18388947515922838363" />
<option value="C:\Users\egore\AppData\Local\Temp\overlay18323405549060409157" />
<option value="C:\Users\egore\AppData\Local\Temp\overlay16485769531165389458" />
<option value="C:\Users\egore\AppData\Local\Temp\overlay7802277587693817774" />
<option value="C:\Users\egore\AppData\Local\Temp\overlay16197097467506260191" />
</paths>
</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,66 @@
package ru.ulstu.`is`.pmu.composeui
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.widget.TextView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun About() {
val localContext = LocalContext.current
val aboutText = localContext.resources.getText(R.string.about_text)
val urlOnClick = {
val openURL = Intent(Intent.ACTION_VIEW)
openURL.data = Uri.parse("https://ulstu.ru/")
localContext.startActivity(openURL)
}
Column(Modifier.padding(all = 10.dp)) {
AndroidView(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = urlOnClick),
factory = { context -> TextView(context) },
update = { it.text = aboutText }
)
Spacer(Modifier.padding(bottom = 10.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = urlOnClick
) {
Text(stringResource(id = R.string.generator))
}
}
}
@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 AboutPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
About()
}
}
}

View File

@ -0,0 +1,282 @@
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.tank.composeui.TankList
import ru.ulstu.`is`.pmu.tank.composeui.list.NationListViewModel
import ru.ulstu.`is`.pmu.tanks.composeui.Account
import ru.ulstu.`is`.pmu.tanks.composeui.Constructor
import ru.ulstu.`is`.pmu.tanks.composeui.Hangar
import ru.ulstu.`is`.pmu.tanks.composeui.NationList
@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.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")
@OptIn(ExperimentalMaterial3Api::class)
@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)
Button(
modifier = Modifier
.width(200.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 = "Добавить")
}
}
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,27 @@
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.tanks.composeui.Hangar
import ru.ulstu.`is`.pmu.tanks.composeui.Login
import ru.ulstu.`is`.pmu.tanks.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,61 @@
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.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
),
StudentView(
"student-view/{id}", R.string.student_view_title, 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,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 NOT_USER_TANK = "notUserTanks"
}

View File

@ -0,0 +1,185 @@
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.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
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 product: NationRemote
): NationRemote
@PUT("${ApiRoutes.NATION}/{id}")
suspend fun updateNation(
@Path("id") id: Long,
@Body product: 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}/{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
// ![TANK]
// :[USER_TANK_CROSS_REF]
@GET(ApiRoutes.USER_TANK)
suspend fun getUserTankCrossRef(): List<UserTankCrossRefRemote>
@POST(ApiRoutes.USER_TANK)
suspend fun insertUserTankCrossRef(
@Body 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,110 @@
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.toLevel
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.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.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,111 @@
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.api.model.toTank
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.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.pmu.tank.repository.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, 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 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, 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,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,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,21 @@
package ru.ulstu.`is`.pmu.tank.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
@Serializable
data class UserTankCrossRefRemote (
val id: Long = 0,
val tankId: Long = 0
)
fun UserTankCrossRefRemote.toUserTankCrossRef(): UserTankCrossRef = UserTankCrossRef(
userId = id,
tankId = tankId
)
fun UserTankCrossRef.toRemote(): UserTankCrossRefRemote = UserTankCrossRefRemote(
id = userId,
tankId = tankId
)

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.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,87 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.map
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
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.NationRemote
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.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.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,128 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import android.graphics.Bitmap
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.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.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.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.OfflineLevelRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineTankRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUsersTanksRepository
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
import java.nio.file.Files.find
class RestTankRepository (
private val service: ServerService,
private val dbTankRepository: OfflineTankRepository,
private val dbUsersTanksRepository: OfflineUsersTanksRepository
) : 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().map{ it.toUserTankCrossRef() }
//спискок, который вернём
var supportList = ArrayList<Tank>()
//вспомогательный список для проверки отсутствия танка у пользователя
var notToUser: List<UserTankCrossRef> = totalList.map{ UserTankCrossRef(userId, it.tankId!!) }
if (totalUserTankList.isEmpty()){
return totalList
} else {
notToUser.forEach(){userTank ->
if(!totalUserTankList.contains(userTank)){
supportList.add(totalList.firstOrNull { it.tankId == userTank.tankId }!!)
}
}
return supportList
}
}
//очень классная штука
private suspend fun <T> Flow<List<T>>.flattenToList() =
flatMapConcat { it.asFlow() }.toList()
override suspend fun getTank(uid: Long): Tank {
return service.getTank(uid).toTank()
}
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().map{ it.toUserTankCrossRef() }
//спискок, который вернём
var supportList = ArrayList<TankWithNationAndLevel>()
//вспомогательный список для проверки наличия танка у пользователя
var notToUser: List<UserTankCrossRef> = totalList.map{ UserTankCrossRef(userId, it.tankId!!) }
if (totalUserTankList.isEmpty()){
return listOf()
} else {
notToUser.forEach(){userTank ->
if(totalUserTankList.contains(userTank)){
val tank = totalList.firstOrNull { it.tankId == userTank.tankId }!!
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) {
service.insertUserTankCrossRef(UserTankCrossRefRemote(userId, tankId))
}
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 delete(tankId: Long) {
service.deleteTank(tankId)
dbTankRepository.delete(tankId)
}
override suspend fun deleteAll() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,45 @@
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.api.model.toUser
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.LevelWithTanks
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.repository.LevelRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineLevelRepository
import ru.ulstu.`is`.pmu.tank.repository.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,305 @@
package ru.ulstu.`is`.pmu.tank.composeui
import android.content.res.Configuration
import androidx.compose.foundation.Image
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.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
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.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
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 androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
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.TankEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.UsersTanksEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.list.TankListUiState
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.tanks.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),
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(
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(
nation: Nation,
tanks: List<Tank>?,
onClick: (uid: Long) -> Unit,
viewModel: UsersTanksEditViewModel
) {
val scope = rememberCoroutineScope()
val tank = viewModel.usersTanksUiState
fun handleTankButtonClick() {
scope.launch {
if (tank.userTankCrossRef.tankId == 0L) {
viewModel.saveUserTank()
}
}
}
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 = { handleTankButtonClick() }
) {
//navController?.navigate(Screen.Hangar.route)
//navController?.navigate(studentId)
//"${student.firstName} ${student.lastName}"
Text(text = stringResource(id = R.string.purchase_button))
}
}
}
}
}
} 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()
)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TankList(
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(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(
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(
nations = NationsListUiState(listOf()),
viewModel = viewModel(factory = AppViewModelProvider.Factory),
listTanks = listOf()
) { }
}
}
}

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,116 @@
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.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankExtra
import ru.ulstu.`is`.pmu.tank.model.TankImage
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import kotlin.math.tan
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,99 @@
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 userUid: Long = checkNotNull(savedStateHandle["id"])
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,36 @@
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 androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.OfflineUsersTanksRepository
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
class UsersTanksEditViewModel(
savedStateHandle: SavedStateHandle,
private val usersTanksRepository: UsersTanksRepository
) : ViewModel() {
var usersTanksUiState by mutableStateOf(UserTankCrossRefUiState())
private set
//val tankId: Long = checkNotNull(savedStateHandle["id"])
private val userId: Long = 100L
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,55 @@
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 val usersTanksRepository: UsersTanksRepository,
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,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.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,58 @@
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.LevelWithTanks
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankExtra
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 WHERE tankId = :id")
suspend fun delete(id: Long)
@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(productImage: TankImage): Long
@Insert
suspend fun insertMany(productImages: List<TankImage>)
@Update
suspend fun update(productImage: TankImage)
@Delete
suspend fun delete(productImage: 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()
}

View File

@ -0,0 +1,84 @@
package ru.ulstu.`is`.pmu.tank.database
import android.content.Context
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.repository.RestLevelRepository
import ru.ulstu.`is`.pmu.tank.api.repository.RestNationRepository
import ru.ulstu.`is`.pmu.tank.api.repository.RestTankRepository
import ru.ulstu.`is`.pmu.tank.api.repository.RestUserRepository
import ru.ulstu.`is`.pmu.tank.repository.LevelRepository
import ru.ulstu.`is`.pmu.tank.repository.NationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineLevelRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineTankRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUserRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUsersTanksRepository
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
interface AppContainer {
val levelRestRepository: RestLevelRepository
val nationRestRepository: RestNationRepository
val userRestRepository: RestUserRepository
val tankRestRepository: RestTankRepository
val usersTanksRepository: UsersTanksRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
private val levelRepository: OfflineLevelRepository by lazy {
OfflineLevelRepository(AppDatabase.getInstance(context).levelDao())
}
private val nationRepository: OfflineNationRepository by lazy {
OfflineNationRepository(AppDatabase.getInstance(context).nationDao())
}
private val tankRepository: OfflineTankRepository by lazy {
OfflineTankRepository(AppDatabase.getInstance(context).tankDao(),
AppDatabase.getInstance(context).usersTanksDao())
}
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
}
override val usersTanksRepository: OfflineUsersTanksRepository by lazy {
OfflineUsersTanksRepository(AppDatabase.getInstance(context).usersTanksDao())
}
override val levelRestRepository: RestLevelRepository by lazy {
RestLevelRepository(
service = ServerService.getInstance(),
dbLevelRepository = levelRepository
)
}
override val nationRestRepository: RestNationRepository by lazy {
RestNationRepository(
service = ServerService.getInstance(),
dbNationRepository = nationRepository,
dbRemoteKeyRepository = remoteKeyRepository,
database = AppDatabase.getInstance(context)
)
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
service = ServerService.getInstance(),
dbUserRepository = userRepository,
)
}
override val tankRestRepository: RestTankRepository by lazy {
RestTankRepository(
service = ServerService.getInstance(),
dbTankRepository = tankRepository,
dbUsersTanksRepository = usersTanksRepository
)
}
}

View File

@ -0,0 +1,181 @@
package ru.ulstu.`is`.pmu.tank.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.tank.dao.LevelDao
import ru.ulstu.`is`.pmu.tank.dao.NationDao
import ru.ulstu.`is`.pmu.tank.dao.RemoteKeysDao
import ru.ulstu.`is`.pmu.tank.dao.TankDao
import ru.ulstu.`is`.pmu.tank.dao.TankImageDao
import ru.ulstu.`is`.pmu.tank.dao.UserDao
import ru.ulstu.`is`.pmu.tank.dao.UsersTanksDao
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.RemoteKeys
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankImage
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserRole
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
//тут, собственно говоря, всё и мутится с БД :)))
@Database(
entities = [
Nation::class,
Level::class,
Tank::class,
User::class,
UserTankCrossRef::class,
TankImage::class,
RemoteKeys::class
],
version = 1,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun nationDao(): NationDao
abstract fun levelDao(): LevelDao
abstract fun tankDao() : TankDao
abstract fun userDao() : UserDao
abstract fun tankImageDao() : TankImageDao
abstract fun usersTanksDao(): UsersTanksDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "22-db"
@Volatile
private var INSTANCE: AppDatabase? = null
private suspend fun populateDatabase(context: Context) {
INSTANCE?.let { database ->
// Nations
val nationDao = database.nationDao()
val nation1 = Nation(1L, "СССР")
val nation2 = Nation(2L, "Германия")
val nation3 = Nation(3L, "США")
val nation4 = Nation(4L, "Великобритания")
val nation5 = Nation(5L, "Франция")
val nation6 = Nation(6L, "Чехословакия")
val nation7 = Nation( 7L, "Швеция")
val nation8 = Nation( 8L, "Китай")
val nation9 = Nation( 9L, "Япония")
val nation10 = Nation(10L, "Италия")
val nation11 = Nation(11L, "Бангладеш")
val nation12 = Nation(12L, "Зимбамбве")
val nation13 = Nation(13L, "Гондурас")
val nation14 = Nation(14L, "Майами")
nationDao.insert(nation1)
nationDao.insert(nation2)
nationDao.insert(nation3)
nationDao.insert(nation4)
nationDao.insert(nation5)
nationDao.insert(nation6)
nationDao.insert(nation7)
nationDao.insert(nation8)
nationDao.insert(nation9)
nationDao.insert(nation10)
nationDao.insert(nation11)
nationDao.insert(nation12)
nationDao.insert(nation13)
nationDao.insert(nation14)
// Levels
val levelDao = database.levelDao()
val level1 = Level(11L, 1)
val level2 = Level(12L, 2)
val level3 = Level(13L, 3)
val level4 = Level(14L, 4)
val level5 = Level(15L, 5)
val level6 = Level(16L, 6)
val level7 = Level(17L, 7)
val level8 = Level(18L, 8)
val level9 = Level(19L, 9)
val level10 = Level(20L, 10)
levelDao.insert(level1)
levelDao.insert(level2)
levelDao.insert(level3)
levelDao.insert(level4)
levelDao.insert(level5)
levelDao.insert(level6)
levelDao.insert(level7)
levelDao.insert(level8)
levelDao.insert(level9)
levelDao.insert(level10)
//images
val tankImageDao = database.tankImageDao()
tankImageDao.insertMany(PrepopulateStore.getProductImages(context))
//Tanks
val tankDao = database.tankDao()
val tank1 = Tank(20L,"МС-1", 1000, PrepopulateStore.getProductMiniature(context, 1L), level1.uid, nation1.uid)
val tank2 = Tank(21L, "Т-34-85", 960000, PrepopulateStore.getProductMiniature(context, 2L), level6.uid, nation1.uid)
val tank10 = Tank(22L, "Pershing", 1260000, PrepopulateStore.getProductMiniature(context, 9L), level8.uid, nation3.uid)
val tank6 = Tank(23L, "Ferdinand", 2500000, PrepopulateStore.getProductMiniature(context, 8L), level8.uid, nation2.uid)
val tank3 = Tank(24L, "ИС-2", 1230000, PrepopulateStore.getProductMiniature(context, 3L), level7.uid, nation1.uid)
val tank4 = Tank(25L, "ИСУ-152", 2350000, PrepopulateStore.getProductMiniature(context, 4L), level8.uid, nation1.uid)
val tank5 = Tank(26L, "Tiger 1", 1430000,PrepopulateStore.getProductMiniature(context, 5L), level7.uid, nation2.uid)
val tank7 = Tank(27L, "Tiger 2", 2500000, PrepopulateStore.getProductMiniature(context, 6L), level8.uid, nation2.uid)
val tank8 = Tank(28L, "Panther", 1350000, PrepopulateStore.getProductMiniature(context, 7L), level7.uid, nation2.uid)
val tank9 = Tank(29L, "M4A2E3", 990000, PrepopulateStore.getProductMiniature(context, 10L), level6.uid, nation3.uid)
val tank11 = Tank(30L, "Hellcat", 940000, PrepopulateStore.getProductMiniature(context, 11L), level7.uid, nation3.uid)
tankDao.insert(tank1)
tankDao.insert(tank2)
tankDao.insert(tank3)
tankDao.insert(tank4)
tankDao.insert(tank5)
tankDao.insert(tank6)
tankDao.insert(tank7)
tankDao.insert(tank8)
tankDao.insert(tank9)
tankDao.insert(tank10)
tankDao.insert(tank11)
//Users
val userDao = database.userDao()
val user = User(100L,"3tankista73", "egor@mail.ru", "12032003", UserRole.ADMIN, 10000000)
userDao.insert(user)
userDao.insert(UserTankCrossRef(user.userId, tank1.tankId ?: 0))
userDao.insert(UserTankCrossRef(user.userId, tank3.tankId ?: 0))
userDao.insert(UserTankCrossRef(user.userId, tank5.tankId ?: 0))
userDao.insert(UserTankCrossRef(user.userId, tank7.tankId ?: 0))
userDao.insert(UserTankCrossRef(user.userId, tank9.tankId ?: 0))
}
}
fun getInstance(appContext: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
AppDatabase::class.java,
DB_NAME
)
// .addCallback(object : Callback() {
// override fun onCreate(db: SupportSQLiteDatabase) {
// super.onCreate(db)
// CoroutineScope(Dispatchers.IO).launch {
// populateDatabase(appContext)
// }
// }
// })
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,44 @@
package ru.ulstu.`is`.pmu.tank.database
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.TypeConverter
import java.io.ByteArrayOutputStream
import java.util.Date
class Converters {
/*
@TypeConverter
fun toUserRole(value: Int): UserRole {
return enumValues<UserRole>()[value]
}
@TypeConverter
fun fromUserRole(value: UserRole): Int {
return value.ordinal
}
*/
@TypeConverter
fun toDate(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun fromDate(date: Date?): Long? {
return date?.time
}
@TypeConverter
fun fromBitmap(bitmap: Bitmap): ByteArray {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 1, outputStream)
return outputStream.toByteArray()
}
@TypeConverter
fun toBitmap(byteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}

View File

@ -0,0 +1,41 @@
package ru.ulstu.`is`.pmu.tank.database
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.application.ui.miniatureBound
import com.application.ui.resize
import com.application.ui.tankImageBound
import ru.ulstu.`is`.pmu.tank.model.TankImage
class PrepopulateStore {
companion object {
fun getProductImages(context: Context): List<TankImage> {
return listOf(
TankImage(id = 1, data = getProductImage(context, 1)),
TankImage(id = 2, data = getProductImage(context, 2)),
TankImage(id = 3, data = getProductImage(context, 3)),
TankImage(id = 4, data = getProductImage(context, 4)),
TankImage(id = 5, data = getProductImage(context, 5)),
TankImage(id = 6, data = getProductImage(context, 6)),
TankImage(id = 7, data = getProductImage(context, 7)),
TankImage(id = 8, data = getProductImage(context, 8)),
TankImage(id = 9, data = getProductImage(context, 9)),
TankImage(id = 10, data = getProductImage(context, 10)),
TankImage(id = 11, data = getProductImage(context, 11))
)
}
fun getProductMiniature(context: Context, imageId: Long): Bitmap {
val inputStream = context.assets.open("${imageId}.jpg")
val bitmap = BitmapFactory.decodeStream(inputStream)
return bitmap.resize(miniatureBound)
}
private fun getProductImage(context: Context, imageName: Int): Bitmap {
val inputStream = context.assets.open("${imageName}.jpg")
val bitmap = BitmapFactory.decodeStream(inputStream)
return bitmap.resize(tankImageBound)
}
}
}

View File

@ -0,0 +1,48 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(
tableName = "levels"
)
data class Level(
@PrimaryKey(autoGenerate = true)
val uid: Long?,
@ColumnInfo(name = "level")
val level: Int,
){
@Ignore
constructor(
level: Int
) : this(null, level)
companion object {
val DEMO_LEVEL = Level(
0,
1
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Level
if (uid != other.uid) return false
if (level != other.level) return false
return true
}
override fun hashCode(): Int {
var result = uid?.hashCode() ?: 0
result = 31 * result + level
return result
}
}

View File

@ -0,0 +1,16 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Embedded
import androidx.room.Relation
//one to many for level and tanks
data class LevelWithTanks (
@Embedded
val level: Level,
@Relation(
parentColumn = "uid",
entityColumn = "levelId"
)
val tanks: List<Tank>
)

View File

@ -0,0 +1,60 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(
tableName = "nations"
)
data class Nation(
@PrimaryKey(autoGenerate = true)
val uid: Long?,
val nationName: String,
) {
@Ignore
constructor(
name: String
) : this(null, name)
companion object {
val DEMO_NATION = Nation(
0,
"СССР"
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Nation
if (uid != other.uid) return false
if (nationName != other.nationName) return false
return true
}
override fun hashCode(): Int {
var result = uid?.hashCode() ?: 0
result = 31 * result + nationName.hashCode()
return result
}
}
fun getNations(): List<Nation> {
return listOf(
Nation("СССР"),
Nation("Германия"),
Nation("США"),
Nation("Великобритания"),
Nation("Франция"),
Nation("Китай"),
Nation("Япония"),
Nation("Италия"),
Nation("Польша"),
Nation("Швеция")
)
}

View File

@ -0,0 +1,16 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Embedded
import androidx.room.Relation
//one to many for nation and tanks
data class NationWithTanks (
@Embedded
val nation: Nation,
@Relation(
parentColumn = "uid",
entityColumn = "nationId"
)
val tanks: List<Tank>
)

View File

@ -0,0 +1,25 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
NATION(Nation::class.simpleName ?: "Nation");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -0,0 +1,73 @@
package ru.ulstu.`is`.pmu.tank.model
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.application.ui.getEmptyBitmap
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.tank.database.Converters
@Entity(
tableName = "tanks",
)
data class Tank(
@PrimaryKey(autoGenerate = true)
val tankId: Long?,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "price")
val price: Int,
val miniature: Bitmap,
@ColumnInfo(name = "levelId", index = true)
val levelId: Long?,
@ColumnInfo(name = "nationId", index = true)
val nationId: Long?,
) {
@Ignore
constructor(
name: String,
price: Int,
miniature: Bitmap,
level: Level,
nation: Nation
) : this(null, name, price, miniature, level.uid, nation.uid)
companion object {
fun getTank(index: Long = 0): Tank {
return Tank(
index,
"Первый танк",
100000,
miniature = getEmptyBitmap(),
1,
1
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Tank
if (tankId != other.tankId) return false
if (name != other.name) return false
if (price != other.price) return false
if (miniature != other.miniature) return false
if (levelId != other.levelId) return false
if (nationId != other.nationId) return false
return true
}
override fun hashCode(): Int {
return (tankId ?: -1) as Int
}
}

View File

@ -0,0 +1,21 @@
package ru.ulstu.`is`.pmu.tank.model
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Embedded
import com.application.ui.getEmptyBitmap
class TankExtra (
@Embedded
val tank: Tank,
val image: Bitmap,
) {
companion object {
fun getEmpty(): TankExtra {
return TankExtra(
tank = Tank.getTank(),
image = getEmptyBitmap(),
)
}
}
}

View File

@ -0,0 +1,34 @@
package ru.ulstu.`is`.pmu.tank.model
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import ru.ulstu.`is`.pmu.tank.database.Converters
@Entity(tableName = "tank_images")
data class TankImage(
@ColumnInfo(name = "image_id")
@PrimaryKey(autoGenerate = true)
val id: Long?,
val data: Bitmap
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TankImage
if (id != other.id) return false
if (data != other.data) return false
return true
}
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + data.hashCode()
return result
}
}

View File

@ -0,0 +1,34 @@
package ru.ulstu.`is`.pmu.tank.model
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import ru.ulstu.`is`.pmu.tank.database.Converters
data class TankWithNationAndLevel (
@PrimaryKey(autoGenerate = true)
val tankId: Long?,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "price")
val price: Int,
@ColumnInfo(name="image")
val image: Bitmap,
val level: Int,
val nationName: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TankWithNationAndLevel
if (tankId != other.tankId) return false
return true
}
override fun hashCode(): Int {
return (tankId ?: -1) as Int
}
}

View File

@ -0,0 +1,17 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
//для работы many to many для получения списка танков для пользователя
data class TankWithUsers(
@Embedded var tank: Tank,
@Relation(
parentColumn = "tankId",
entityColumn = "userId",
associateBy = Junction(UserTankCrossRef::class,)
)
var users: List<User>
)

View File

@ -0,0 +1,72 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(
tableName = "users"
)
data class User (
@PrimaryKey(autoGenerate = true)
val userId: Long,
@ColumnInfo(name = "nickname")
val nickname: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "password")
val password: String,
@ColumnInfo(name = "role")
val role: UserRole,
@ColumnInfo(name = "balance")
val balance: Int
){
@Ignore
constructor(
nickname: String,
email: String,
password: String,
role: UserRole,
balance: Int
) : this(0L, nickname, email, password, UserRole.ADMIN, balance)
companion object {
fun getUser(index: Long = 0L): User {
return User(
index,
"3tankista73",
"egor@mail.ru",
"1234567890!",
UserRole.USER,
10000000
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as User
if (userId != other.userId) return false
if (nickname != other.nickname) return false
if (email != other.email) return false
if (password != other.password) return false
if (role != other.role) return false
if (balance != other.balance) return false
return true
}
override fun hashCode(): Int {
var result = userId.hashCode()
result = 31 * result + nickname.hashCode()
result = 31 * result + email.hashCode()
result = 31 * result + password.hashCode()
result = 31 * result + role.hashCode()
result = 31 * result + balance
return result
}
}

View File

@ -0,0 +1,6 @@
package ru.ulstu.`is`.pmu.tank.model
enum class UserRole(val value: Int) {
USER(0),
ADMIN(1)
}

View File

@ -0,0 +1,27 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import org.jetbrains.annotations.NotNull
//many to many for user and tank
@Entity(
tableName = "users_tanks",
primaryKeys = ["userId", "tankId"]
)
data class UserTankCrossRef(
val userId: Long,
val tankId: Long
){
companion object {
fun getEmpty(): UserTankCrossRef {
return UserTankCrossRef(
userId = 0,
tankId = 0
)
}
}
}

View File

@ -0,0 +1,19 @@
package ru.ulstu.`is`.pmu.tank.model
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Junction
import androidx.room.Relation
//для работы many to many для получения списка танков для пользователя
data class UserWithTanks(
@Embedded val user: User,
@Relation(
entity = Tank::class,
parentColumn = "userId",
entityColumn = "tankId",
associateBy = Junction(UserTankCrossRef::class)
)
val tanks: List<TankWithNationAndLevel>
)

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