Compare commits

...

38 Commits

Author SHA1 Message Date
ElEgEv
a7fe8a973c . 2023-12-16 17:02:20 +04:00
ElEgEv
36dd3727b9 . 2023-12-16 16:30:41 +04:00
ElEgEv
e3aa84cd81 LabWork04 2023-12-12 09:35:43 +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
2132 changed files with 155063 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,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="ModuleClassLoaderOverlays">
<paths>
<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,91 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
}
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")
// 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")
// Paging | +
val pagingVersion = "3.3.0-alpha02"
implementation("androidx.paging:paging-compose:$pagingVersion")
}

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,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".TankApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.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,279 @@
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.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.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 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.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?,
viewModel: TankListViewModel = viewModel(factory = AppViewModelProvider.Factory),
listNations: NationDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val tankListUiState by viewModel.tankListUiState.collectAsState()
// Lazy Column, Pass the numbers array
if (navController != null) {
TankList(
nations = listNations.nationsListUiState,
listTanks = tankListUiState.tankList
) { 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
) {
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 ->
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 = { onClick(tank.tankId) }
) {
//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>,
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)
}
// 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()),
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()),
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,121 @@
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)
.filterNotNull()
.first()
.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
&& imageId !! != 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,
imageId = imageId
)
fun Tank.toDetails(): TankDetails = TankDetails(
name = name,
price = price,
levelId = levelId,
nationId = nationId,
miniature = miniature,
imageId = imageId
)
fun Tank.toUiState(isEntryValid: Boolean = false): TankUiState = TankUiState(
tankDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,95 @@
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.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 userUid: Long = 100L
init {
viewModelScope.launch {
if (userUid > 0) {
userUiState = userRepository.getSimpleUser(userUid)
.filterNotNull()
.first()
.toUiState(true)
}
}
}
fun updateUiState(userDetails: UserDetails) {
userUiState = UserUiState(
userDetails = userDetails,
isEntryValid = validateInput(userDetails)
)
}
suspend fun saveUser() {
if (validateInput()) {
if (userUid > 0) {
userRepository.updateUser(userUiState.userDetails.toUser(userUid))
} else {
userRepository.insertUser(userUiState.userDetails.toUser())
}
}
}
private fun validateInput(uiState: UserDetails = userUiState.userDetails): Boolean {
return with(uiState) {
nickname.isNotBlank()
&& email.isNotBlank()
&& password.isNotBlank()
&& 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 balance: Int = 0,
)
fun UserDetails.toUser(uid: Long = 0): User = User(
userId = uid,
nickname = nickname,
email = email,
password = password,
balance = balance
)
fun User.toDetails(): UserDetails = UserDetails(
nickname = nickname,
email = email,
password = password,
balance = balance
)
fun User.toUiState(isEntryValid: Boolean = false): UserUiState = UserUiState(
userDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,21 @@
package ru.ulstu.`is`.pmu.tank.composeui.list
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import ru.ulstu.`is`.pmu.tank.repository.NationRepository
class NationListViewModel(
nationRepository: NationRepository
) : ViewModel() {
private val pager = Pager(
config = PagingConfig(pageSize = 10),
pagingSourceFactory = {
nationRepository.pagingSource()
}
)
val nationPagingFlow = pager.flow.cachedIn(viewModelScope)
}

View File

@ -0,0 +1,45 @@
package ru.ulstu.`is`.pmu.tank.composeui.list
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
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.repository.TankRepository
class TankListViewModel(
private val tankRepository: TankRepository,
private var userId: Long = 100L
) : ViewModel() {
val tankListUiState: StateFlow<TankListUiState> = tankRepository.getForUserAll(userId).map {
TankListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = TankListUiState()
)
val usersTanksUiState: StateFlow<UserTankListUiState> = tankRepository.getUserTanks(userId).map {
UserTankListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = UserTankListUiState()
)
suspend fun deleteTank(tank: Tank) {
tankRepository.deleteTank(tank)
}
fun setUserId(uid: Long){
userId = uid
}
}
data class TankListUiState(val tankList: List<Tank> = listOf())
data class UserTankListUiState(val userTankList: List<TankWithNationAndLevel> = listOf())

View File

@ -0,0 +1,41 @@
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
@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): Flow<Level?>
@Insert
suspend fun insert(level: Level)
@Update
suspend fun update(level: Level)
@Delete
suspend fun delete(level: Level)
}

View File

@ -0,0 +1,46 @@
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>
//получить конкретную нацию
@Query("select * from nations where nations.uid = :uid")
fun getNationUid(uid: Long): Flow<NationWithTanks?>
//получить нацию без списка танков
@Query(
"SELECT nationName FROM nations where nations.uid = :uid"
)
open fun getSimpleNationUid(uid: Long): Flow<Nation?>
@Query("SELECT * FROM nations")
fun pagingSource(): PagingSource<Int, Nation>
@Insert
suspend fun insert(nation: Nation)
@Update
suspend fun update(nation: Nation)
@Delete
suspend fun delete(nation: Nation)
}

View File

@ -0,0 +1,54 @@
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(): Flow<List<Tank>>
//получить конкретный танк
@Query("select t.*, ti.data from tanks AS t " +
"LEFT JOIN tank_images as ti on t.image_id = ti.image_id " +
"where t.tankId = :uid")
fun getTankUid(uid: Long): Flow<Tank?>
//получаем все танки пользователя по его Id
@Query(
"SELECT t.tankId, t.name, t.price, t.image_id, l.level, n.nationName, ti.data AS image FROM UserTankCrossRef AS ut " +
"LEFT JOIN tanks as t on ut.tankId = t.tankId " +
"LEFT JOIN tank_images as ti on t.image_id = ti.image_id " +
"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): Flow<List<TankWithNationAndLevel>>
@Query(
"SELECT t.* FROM tanks AS t WHERE t.tankId NOT IN (SELECT ut.tankId FROM UserTankCrossRef AS ut WHERE ut.userId = :uid)"
)
fun getNotUserTank(uid: Long): Flow<List<Tank>>
@Insert
suspend fun insert(tank: 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)
}

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,48 @@
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
import ru.ulstu.`is`.pmu.tank.model.UserWithTanks
@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 UserTankCrossRef 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 = :uid")
fun getSimpleUserUid(uid: Long): Flow<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,39 @@
package ru.ulstu.`is`.pmu.tank.database
import android.content.Context
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.OfflineTankRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUserRepository
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
interface AppContainer {
val levelRepository: LevelRepository
val nationRepository: NationRepository
val tankRepository: TankRepository
val userRepository: UserRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val levelRepository: LevelRepository by lazy {
OfflineLevelRepository(AppDatabase.getInstance(context).levelDao())
}
override val nationRepository: NationRepository by lazy {
OfflineNationRepository(AppDatabase.getInstance(context).nationDao())
}
override val tankRepository: TankRepository by lazy {
OfflineTankRepository(
AppDatabase.getInstance(context).tankDao(),
tankImageDao = AppDatabase.getInstance(context).tankImageDao())
}
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
companion object {
const val TIMEOUT = 5000L
}
}

View File

@ -0,0 +1,164 @@
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.TankDao
import ru.ulstu.`is`.pmu.tank.dao.TankImageDao
import ru.ulstu.`is`.pmu.tank.dao.UserDao
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.TankImage
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.model.UserWithTanks
//тут, собственно говоря, всё и мутится с БД :)))
@Database(entities = [Nation::class, Level::class, Tank::class, User::class, UserTankCrossRef::class, TankImage::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
companion object {
private const val DB_NAME: String = "19-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), 1L, level1.uid, nation1.uid)
val tank2 = Tank(21L, "Т-34-85", 960000, PrepopulateStore.getProductMiniature(context, 2L), 2L, level6.uid, nation1.uid)
val tank10 = Tank(22L, "Pershing", 1260000, PrepopulateStore.getProductMiniature(context, 9L), 9L, level8.uid, nation3.uid)
val tank6 = Tank(23L, "Ferdinand", 2500000, PrepopulateStore.getProductMiniature(context, 8L), 8L, level8.uid, nation2.uid)
val tank3 = Tank(24L, "ИС-2", 1230000, PrepopulateStore.getProductMiniature(context, 3L), 3L, level7.uid, nation1.uid)
val tank4 = Tank(25L, "ИСУ-152", 2350000, PrepopulateStore.getProductMiniature(context, 4L), 4L, level8.uid, nation1.uid)
val tank5 = Tank(26L, "Tiger 1", 1430000,PrepopulateStore.getProductMiniature(context, 5L), 5L, level7.uid, nation2.uid)
val tank7 = Tank(27L, "Tiger 2", 2500000, PrepopulateStore.getProductMiniature(context, 6L), 6L, level8.uid, nation2.uid)
val tank8 = Tank(28L, "Panther", 1350000, PrepopulateStore.getProductMiniature(context, 7L), 7L, level7.uid, nation2.uid)
val tank9 = Tank(29L, "M4A2E3", 990000, PrepopulateStore.getProductMiniature(context, 10L), 10L, level6.uid, nation3.uid)
val tank11 = Tank(30L, "Hellcat", 940000, PrepopulateStore.getProductMiniature(context, 11L), 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", 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,87 @@
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",
foreignKeys = [
ForeignKey(
entity = TankImage::class,
parentColumns = ["image_id"],
childColumns = ["image_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.RESTRICT
)
]
)
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 = "image_id", index = true)
val imageId: Long,
@ColumnInfo(name = "levelId", index = true)
val levelId: Long?,
@ColumnInfo(name = "nationId", index = true)
val nationId: Long?,
) {
@Ignore
constructor(
name: String,
price: Int,
miniature: Bitmap,
imageId: Long,
level: Level,
nation: Nation
) : this(null, name, price, miniature, imageId, level.uid, nation.uid)
companion object {
fun getTank(index: Long = 0): Tank {
return Tank(
index,
"Первый танк",
100000,
miniature = getEmptyBitmap(),
1L,
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 (imageId != other.imageId) 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,66 @@
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 = "balance")
val balance: Int
){
@Ignore
constructor(
nickname: String,
email: String,
password: String,
balance: Int
) : this(0L, nickname, email, password, balance)
companion object {
fun getUser(index: Long = 0L): User {
return User(
index,
"3tankista73",
"egor@mail.ru",
"1234567890!",
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 (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 + balance
return result
}
}

View File

@ -0,0 +1,15 @@
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(primaryKeys = ["userId", "tankId"])
data class UserTankCrossRef(
val userId: Long,
val tankId: Long
)

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

View File

@ -0,0 +1,14 @@
package ru.ulstu.`is`.pmu.tank.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.LevelWithTanks
interface LevelRepository {
suspend fun getAllLevels(): List<Level>
fun getSimpleLevel(uid: Long): Flow<Level?>
fun getFullLevel(uid: Long): Flow<LevelWithTanks?>
suspend fun insertLevel(level: Level)
suspend fun updateLevel(level: Level)
suspend fun deleteLevel(level: Level)
}

View File

@ -0,0 +1,16 @@
package ru.ulstu.`is`.pmu.tank.repository
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.NationWithTanks
interface NationRepository {
suspend fun getAllNations(): List<Nation>
fun getSimpleNation(uid: Long): Flow<Nation?>
fun getFullNation(uid: Long): Flow<NationWithTanks?>
fun pagingSource(): PagingSource<Int, Nation>
suspend fun insertNation(nation: Nation)
suspend fun updateNation(nation: Nation)
suspend fun deleteNation(nation: Nation)
}

View File

@ -0,0 +1,20 @@
package ru.ulstu.`is`.pmu.tank.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.dao.LevelDao
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.LevelWithTanks
class OfflineLevelRepository(private val levelDao: LevelDao) : LevelRepository {
override suspend fun getAllLevels(): List<Level> = levelDao.getAll()
override fun getSimpleLevel(uid: Long): Flow<Level?> = levelDao.getSimpleLevelUid(uid)
override fun getFullLevel(uid: Long): Flow<LevelWithTanks?> = levelDao.getLevelUid(uid)
override suspend fun insertLevel(level: Level) = levelDao.insert(level)
override suspend fun updateLevel(level: Level) = levelDao.update(level)
override suspend fun deleteLevel(level: Level) = levelDao.delete(level)
}

View File

@ -0,0 +1,23 @@
package ru.ulstu.`is`.pmu.tank.repository
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.dao.NationDao
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.NationWithTanks
class OfflineNationRepository(private val nationDao: NationDao) : NationRepository {
override suspend fun getAllNations(): List<Nation> = nationDao.getAll()
override fun getSimpleNation(uid: Long): Flow<Nation?> = nationDao.getSimpleNationUid(uid)
override fun getFullNation(uid: Long): Flow<NationWithTanks?> = nationDao.getNationUid(uid)
override fun pagingSource(): PagingSource<Int, Nation> = nationDao.pagingSource()
override suspend fun insertNation(nation: Nation) = nationDao.insert(nation)
override suspend fun updateNation(nation: Nation) = nationDao.update(nation)
override suspend fun deleteNation(nation: Nation) = nationDao.delete(nation)
}

View File

@ -0,0 +1,52 @@
package ru.ulstu.`is`.pmu.tank.repository
import android.graphics.Bitmap
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.dao.TankDao
import ru.ulstu.`is`.pmu.tank.dao.TankImageDao
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.model.TankWithNationAndLevel
class OfflineTankRepository(
private val tankDao: TankDao,
private val tankImageDao: TankImageDao
) : TankRepository {
override fun getAll(): Flow<List<Tank>> = tankDao.getAll()
override fun getForUserAll(userId: Long): Flow<List<Tank>> = tankDao.getNotUserTank(userId)
override fun getTank(uid: Long): Flow<Tank?> = tankDao.getTankUid(uid)
override fun getUserTanks(uid: Long): Flow<List<TankWithNationAndLevel>> = tankDao.getUserTanks(uid)
override suspend fun insertTank(tank: Tank, image: Bitmap) {
val imageId = tankImageDao.insert(
TankImage(
id = null,
data = image
)
)
tankDao.insert(tank.copy(imageId = imageId))
}
override suspend fun updateTank(tank: Tank, image: Bitmap) {
val imageId = tankImageDao.insert(
TankImage(
id = null,
data = image
)
)
tankDao.update(tank.copy(imageId = imageId))
tankImageDao.delete(tank.imageId)
}
override suspend fun deleteTank(tank: Tank) = tankDao.delete(tank)
override suspend fun delete(tankId: Long) {
tankDao.delete(tankId)
}
}

View File

@ -0,0 +1,20 @@
package ru.ulstu.`is`.pmu.tank.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.dao.UserDao
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.User
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override fun getSimpleUser(uid: Long): Flow<User?> = userDao.getSimpleUserUid(uid)
override fun getFullUser(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>> = userDao.getUserUid(uid)
override suspend fun insertUser(student: User) = userDao.insert(student)
override suspend fun updateUser(student: User) = userDao.update(student)
override suspend fun deleteUser(student: User) = userDao.delete(student)
}

View File

@ -0,0 +1,18 @@
package ru.ulstu.`is`.pmu.tank.repository
import android.graphics.Bitmap
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankExtra
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
interface TankRepository {
fun getAll(): Flow<List<Tank>>
fun getForUserAll(userId: Long): Flow<List<Tank>>
fun getTank(uid: Long): Flow<Tank?>
fun getUserTanks(uid: Long): Flow<List<TankWithNationAndLevel>>
suspend fun insertTank(tank: Tank, image: Bitmap)
suspend fun updateTank(tank: Tank, image: Bitmap)
suspend fun deleteTank(tank: Tank)
suspend fun delete(tankId: Long)
}

View File

@ -0,0 +1,14 @@
package ru.ulstu.`is`.pmu.tank.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.User
interface UserRepository {
fun getAllUsers(): Flow<List<User>>
fun getSimpleUser(uid: Long): Flow<User?>
fun getFullUser(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>>
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,108 @@
package ru.ulstu.`is`.pmu.tanks.composeui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.overscroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.composeui.navigation.Screen
import ru.ulstu.`is`.pmu.tank.composeui.list.NationListViewModel
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tanks.composeui.image.Dimensions
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NationList(
navController: NavController,
viewModel: NationListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val nations = viewModel.nationPagingFlow.collectAsLazyPagingItems()
fun handleAddButtonClick() {
val route = Screen.getEntityRoute(Screen.Constructor, 0)
navController.navigate(route)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier.height(300.dp * nations.itemCount / 5),
) {
items(
count = nations.itemCount,
key = nations.itemKey { it.uid!! }
) {
val nation: Nation = nations[it] ?: return@items
NationBox(
nation = nation,
navController = navController
)
}
}
}
}
@Composable
fun NationBox(
nation: Nation,
navController: NavController
) {
fun handleOnClick() {
val route = Screen.getEntityRoute(Screen.EDIT_NATIONS, nation.uid!!)
navController.navigate(route)
}
Box(
modifier = Modifier
.fillMaxWidth()
.clickable(
onClick = {
handleOnClick()
}
)
.background(
MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(Dimensions.cornerRadius)
)
.padding(15.dp)
) {
Text(
text = nation.nationName,
color = MaterialTheme.colorScheme.onSurface
)
}
}

View File

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

View File

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

View File

@ -0,0 +1,31 @@
package ru.ulstu.`is`.pmu.tanks.composeui.image
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.unit.dp
@Composable
fun CuteImageButton(
imageBitmap: ImageBitmap,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val cornerShape = RoundedCornerShape(Dimensions.cornerRadius)
Button(
onClick = { onClick() },
modifier = modifier,
shape = cornerShape,
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
)
) {
CuteImage(imageBitmap)
}
}

View File

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

View File

@ -0,0 +1,61 @@
package ru.ulstu.`is`.pmu.tanks.composeui.image
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntSize
@Composable
fun CuteZoomableImage(
imageBitmap: ImageBitmap
) {
val size = remember { mutableStateOf(IntSize.Zero) }
val scale = remember { mutableFloatStateOf(1f) }
val offsetX = remember { mutableFloatStateOf(0f) }
val offsetY = remember { mutableFloatStateOf(0f) }
Box(
modifier = Modifier
.clip(RectangleShape)
.fillMaxSize()
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scale.floatValue = maxOf(1f, minOf(3f, scale.floatValue * zoom))
val maxX = (size.value.width * (scale.floatValue - 1)) / 2
val minX = -maxX
offsetX.floatValue = maxOf(minX, minOf(maxX, offsetX.floatValue + pan.x))
val maxY = (size.value.height * (scale.floatValue - 1)) / 2
val minY = -maxY
offsetY.floatValue = maxOf(minY, minOf(maxY, offsetY.floatValue + pan.y))
}
}.onSizeChanged {
size.value = it
}
) {
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.graphicsLayer(
scaleX = scale.floatValue,
scaleY = scale.floatValue,
translationX = offsetX.floatValue,
translationY = offsetY.floatValue
)
)
}
}

View File

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

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