так и живем

This commit is contained in:
Максим 2023-12-20 13:10:55 +03:00
parent c9c689dbbc
commit 37f2701ce7
160 changed files with 99747 additions and 2266 deletions

41
.gitignore vendored
View File

@ -1,26 +1,15 @@
# ---> Kotlin
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

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

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
My Application

View File

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/compiler.xml Normal file
View File

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

20
.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>

6
.idea/kotlinc.xml Normal file
View File

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

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<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$/out" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

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

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

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

14
app/.idea/gradle.xml Normal file
View File

@ -0,0 +1,14 @@
<?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" />
</GradleProjectSettings>
</option>
</component>
</project>

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

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

6
app/.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>

View File

@ -1,18 +1,17 @@
import org.jetbrains.kotlin.kapt3.base.Kapt.kapt
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "com.example.labwork"
compileSdk = 34
namespace = "com.example.myapplication"
compileSdk = 33
defaultConfig {
applicationId = "com.example.labwork"
minSdk = 24
applicationId = "com.example.myapplication"
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0"
@ -33,6 +32,7 @@ android {
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
@ -43,7 +43,7 @@ android {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
kotlinCompilerExtensionVersion = "1.4.5"
}
packaging {
resources {
@ -53,39 +53,42 @@ android {
}
dependencies {
implementation("org.threeten:threetenbp:1.5.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("io.github.vanpra.compose-material-dialogs:datetime:0.8.1-rc")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
implementation("androidx.navigation:navigation-compose:2.7.4")
implementation("androidx.compose.material:material:1.5.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
implementation("androidx.room:room-ktx:2.6.0")
kapt("androidx.room:room-compiler:2.6.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
val paging_version = "3.1.1"
implementation ("androidx.paging:paging-runtime:$paging_version")
// alternatively - without Android dependencies for tests
testImplementation ("androidx.paging:paging-common:$paging_version")
// optional - RxJava2 support
implementation ("androidx.paging:paging-rxjava2:$paging_version")
// optional - RxJava3 support
implementation ("androidx.paging:paging-rxjava3:$paging_version")
// optional - Guava ListenableFuture support
implementation ("androidx.paging:paging-guava:$paging_version")
// optional - Jetpack Compose integration
implementation ("androidx.paging:paging-compose:1.0.0-alpha18")
// Core
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
// UI
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.navigation:navigation-compose:2.6.0")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.compose.material:material:1.4.3")
// Room
val room_version = "2.5.2"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
implementation("androidx.room:room-paging:$room_version")
// retrofit
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("androidx.paging:paging-compose:3.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
// Tests
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
@ -93,4 +96,6 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
//
}

View File

@ -1,13 +1,11 @@
package com.example.labwork
package com.example.myapplication
import androidx.test.platform.app.InstrumentationRegistry
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
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
@ -19,6 +17,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.labwork", appContext.packageName)
assertEquals("com.example.myapplication", appContext.packageName)
}
}

View File

@ -2,22 +2,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".BikeApplication"
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:name=".BicycleRentApplication"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LabWork"
tools:targetApi="31">
android:theme="@style/Theme.Pmudemo"
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:name=".MainComposeActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.LabWork">
android:theme="@style/Theme.Pmudemo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

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

View File

@ -1,32 +0,0 @@
package com.example.labwork
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.labwork.button_navigation.MainScreen
import com.example.labwork.database.AppDatabase
import com.example.labwork.ui.theme.LabWorkTheme
import com.example.labwork.viewmodel.AppViewModelProvider
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
class MainActivity : ComponentActivity() {
private val userViewModel: UserViewModel by viewModels()
private val bicycleViewModel: BicycleViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LabWorkTheme {
MainScreen(viewModel(factory = AppViewModelProvider.Factory), userViewModel = viewModel(factory = AppViewModelProvider.Factory))
}
}
}
}

View File

@ -1,36 +0,0 @@
package com.example.labwork.button_navigation
import android.annotation.SuppressLint
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.DAO.UserDao
import com.example.labwork.repository.OfflineBicycleRepository
import com.example.labwork.repository.OfflineUserRepository
import com.example.labwork.viewmodel.AppViewModelProvider
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun MainScreen(bicycleViewModel: BicycleViewModel = viewModel(factory = AppViewModelProvider.Factory),
userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val navController = rememberNavController()
Scaffold(
bottomBar = {
SlideNavigation(navController = navController)
}
) {
SlideGraph(
navHostController = navController,
bicycleViewModel = bicycleViewModel,
userViewModel = userViewModel
)
}
}

View File

@ -1,104 +0,0 @@
package com.example.labwork.button_navigation
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.labwork.pages.ListInfo
import com.example.labwork.pages.product.FormNewProduct
import com.example.labwork.pages.product.ListProduct
import com.example.labwork.pages.user.RegisteryOrLogin
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.AppViewModelProvider
import com.example.labwork.viewmodel.BicycleScreenViewModel
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
@Composable
fun ScreenInfo() {
ListInfo()
}
@Composable
fun ScreenProfile(userViewModel: UserViewModel, navHostController: NavHostController, bicycleViewModel: BicycleViewModel) {
RegisteryOrLogin(userViewModel, navHostController, bicycleViewModel)
}
@Composable
fun ScreenListProduct(
bicycleViewModel: BicycleViewModel,
navHostController: NavHostController,
bicycleScreenViewModel: BicycleScreenViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val bicycles = bicycleScreenViewModel.bicycles.collectAsLazyPagingItems()
LaunchedEffect(Unit) {
bicycleViewModel.fetchBicycles()
}
Column(
modifier = Modifier.fillMaxHeight().padding(bottom = 65.dp)
) {
FormNewProduct(bicycleViewModel, navHostController)
LazyColumn {
items(count = bicycles.itemCount) { index ->
val bicycle = bicycles[index]
if (bicycle != null) {
ListProduct(
item = bicycle,
bicycleViewModel = bicycleViewModel,
navHostController = navHostController
)
}
}
bicycles.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = LightBluePolitech) }
}
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = LightBluePolitech) }
}
loadState.refresh is LoadState.Error -> {
val err = bicycles.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = bicycles.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}
}

View File

@ -1,34 +0,0 @@
package com.example.labwork.button_navigation
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.example.labwork.repository.OfflineBicycleRepository
import com.example.labwork.repository.OfflineUserRepository
import com.example.labwork.viewmodel.AppViewModelProvider
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
@Composable
fun SlideGraph(
navHostController: NavHostController,
bicycleViewModel: BicycleViewModel = viewModel(factory = AppViewModelProvider.Factory),
userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
NavHost(navController = navHostController, startDestination = "Profile") {
composable("Profile") {
ScreenProfile(userViewModel, bicycleViewModel = bicycleViewModel, navHostController = navHostController)
}
composable("Info") {
ScreenInfo()
}
composable("ListProduct") {
ScreenListProduct(bicycleViewModel, navHostController = navHostController)
}
}
}

View File

@ -1,9 +0,0 @@
package com.example.labwork.button_navigation
import com.example.labwork.R
sealed class SlideItem(val title: String, val iconId: Int, val route: String){
object ScreenInfo: SlideItem("Информация", R.drawable.baseline_info,"Info" )
object ScreenProfile: SlideItem("Профиль", R.drawable.baseline_account_circle,"Profile" )
object ScreenListProduct: SlideItem("Товары", R.drawable.baseline_manage_search,"ListProduct" )
}

View File

@ -1,67 +0,0 @@
package com.example.labwork.button_navigation
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon
import androidx.compose.material.SnackbarDefaults.backgroundColor
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
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 com.example.labwork.ui.theme.DarkBluePolitech
import com.example.labwork.ui.theme.LightBluePolitech
@Composable
fun SlideNavigation(
navController: NavController
) {
val listItems = listOf(
SlideItem.ScreenInfo,
SlideItem.ScreenProfile,
SlideItem.ScreenListProduct
)
BottomNavigation(
backgroundColor = Color.White,
modifier = Modifier.padding(10.dp),
elevation = 5.dp
) {
val backStackEntry by navController.currentBackStackEntryAsState()
val currentRout = backStackEntry?.destination?.route
listItems.forEach { item ->
BottomNavigationItem(
selected = currentRout == item.route,
onClick = {
navController.navigate(item.route)
},
icon = {
Icon(
painter = painterResource(id = item.iconId),
contentDescription = "Icon"
)
},
label = {
Text(
text = item.title,
fontSize = 9.sp
)
},
selectedContentColor = LightBluePolitech,
unselectedContentColor = DarkBluePolitech
)
}
}
}

View File

@ -1,25 +0,0 @@
package com.example.labwork.database
import android.content.Context
import com.example.labwork.repository.BicycleRepository
import com.example.labwork.repository.OfflineBicycleRepository
import com.example.labwork.repository.OfflineUserRepository
import com.example.labwork.repository.UserRepository
interface AppContainer {
val userRepository: UserRepository
val bicycleRepository: BicycleRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
override val bicycleRepository: BicycleRepository by lazy {
OfflineBicycleRepository(AppDatabase.getInstance(context).bicycleDao())
}
companion object {
const val TIMEOUT = 5000L
}
}

View File

@ -1,82 +0,0 @@
package com.example.labwork.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.DAO.UserDao
import com.example.labwork.models.Bicycle
import com.example.labwork.models.User
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.concurrent.Executors
@Database(entities = [User::class, Bicycle::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun bicycleDao(): BicycleDao
companion object {
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
if (instance == null) {
synchronized(AppDatabase::class) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"my-database"
)
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Executors.newSingleThreadExecutor().execute {
CoroutineScope(Dispatchers.IO).launch {
populateDatabase(instance!!)
}
}
}
})
.build()
}
}
return instance!!
}
private suspend fun populateDatabase(database: AppDatabase) {
val userDao = database.userDao()
val bicycleDao = database.bicycleDao()
// Создание пользователей
val user1 = User(null, "John Doe", "john.doe@example.com", "password123")
val user2 = User(null, "Jane Smith", "jane.smith@example.com", "password456")
// Вставка пользователей в базу данных
userDao.insertUser(user1)
userDao.insertUser(user2)
// Создание велосипедов
val bicycle1 = Bicycle(brand = "Trek", model = "Велосипед 1", color = "Black", userId = 1, id = null)
val bicycle2 = Bicycle(brand = "Trek", model = "Велосипед 2", color = "Black", userId = null, id = null)
val bicycle3 = Bicycle(brand = "Trek", model = "Велосипед 3", color = "Black", userId = 1, id = null)
val bicycle4 = Bicycle(brand = "Trek", model = "Велосипед 4", color = "Black", userId = null, id = null)
val bicycle5 = Bicycle(brand = "Trek", model = "Велосипед 5", color = "Black", userId = null, id = null)
val bicycle6 = Bicycle(brand = "Trek", model = "Велосипед 6", color = "Black", userId = null, id = null)
// Вставка велосипедов в базу данных
bicycleDao.insertBicycle(bicycle1)
bicycleDao.insertBicycle(bicycle2)
bicycleDao.insertBicycle(bicycle3)
bicycleDao.insertBicycle(bicycle4)
bicycleDao.insertBicycle(bicycle5)
bicycleDao.insertBicycle(bicycle6)
}
}
}

View File

@ -1,32 +0,0 @@
package com.example.labwork.database.DAO
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.labwork.models.Bicycle
@Dao
interface BicycleDao {
@Insert
suspend fun insertBicycle(bicycle: Bicycle)
@Update
suspend fun updateBicycle(bicycle: Bicycle)
@Delete
suspend fun deleteBicycle(bicycle: Bicycle)
@Query("SELECT * FROM bicycles")
suspend fun getAllBicycles(): List<Bicycle>
@Query("SELECT * FROM bicycles WHERE id = :bicycleId")
suspend fun getBicycleById(bicycleId: Int?): Bicycle
@Query("SELECT * FROM bicycles WHERE userId = :userId")
suspend fun getBicyclesByUserId(userId: Int): List<Bicycle>
@Query("SELECT * FROM bicycles ORDER BY id ASC LIMIT :limit OFFSET :offset")
suspend fun getBicycles(limit: Int, offset: Int): List<Bicycle>
}

View File

@ -1,35 +0,0 @@
package com.example.labwork.database.DAO
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.labwork.models.User
@Dao
interface UserDao {
@Insert
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User
@Query("SELECT * FROM users WHERE email = :email")
suspend fun getUserByEmail(email: String): User
@Query("SELECT password FROM users WHERE email = :email")
suspend fun getPasswordByEmail(email: String): String
@Query("SELECT * FROM users WHERE email = :email AND password = :password")
suspend fun getUserByEmailAndPassword(email: String, password: String): User?
}

View File

@ -1,37 +0,0 @@
package com.example.labwork.database.Paging
import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.models.Bicycle
import kotlinx.coroutines.delay
class BicyclePagingSource(private val dao: BicycleDao,
) : PagingSource<Int, Bicycle>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Bicycle> {
val page = params.key ?: 0
return try {
Log.d("MainPagingSource", "load: $page")
val entities = dao.getBicycles(params.loadSize, page * params.loadSize)
if (page != 0) delay(2000)
LoadResult.Page(
data = entities,
prevKey = if (page == 0) null else page - 1,
nextKey = if (entities.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override val jumpingSupported: Boolean = true
override fun getRefreshKey(state: PagingState<Int, Bicycle>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}

View File

@ -1,19 +0,0 @@
package com.example.labwork.models
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "bicycles",
foreignKeys = [ForeignKey(entity = User::class,
parentColumns = ["id"],
childColumns = ["userId"],
onDelete = ForeignKey.SET_NULL)])
data class Bicycle(
@PrimaryKey(autoGenerate = true)
val id: Int?,
var brand: String,
var model: String,
var color: String,
val userId: Int?
)

View File

@ -1,15 +0,0 @@
package com.example.labwork.models
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int?,
var name: String,
var email: String,
var password: String,
)

View File

@ -1,338 +0,0 @@
package com.example.labwork.pages
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.labwork.R
import com.example.labwork.ui.theme.LightBluePolitech
@Composable
fun ListInfo() {
var selectedTab by remember { mutableStateOf(0) }
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.padding(16.dp)
.align(Alignment.TopCenter)
) {
Box(
modifier = Modifier
.size(200.dp)
.align(CenterHorizontally)
.clip(shape = RoundedCornerShape(8.dp))
.shadow(16.dp, shape = RoundedCornerShape(8.dp))
) {
Image(
painter = painterResource(id = R.drawable.baseline_directions_bike_24),
contentDescription = "Menu Image",
Modifier.fillMaxSize()
)
}
Spacer(modifier = Modifier.height(16.dp))
TabRow(
selectedTabIndex = selectedTab,
backgroundColor = LightBluePolitech,
contentColor = Color.White,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
color = Color.White,
height = 4.dp,
modifier = Modifier
.tabIndicatorOffset(tabPositions[selectedTab])
)
},
modifier = Modifier.shadow(16.dp)
) {
Tab(
selected = selectedTab == 0,
onClick = { selectedTab = 0 }
) {
Text(
text = "Университет",
modifier = Modifier.padding(8.dp),
textAlign = TextAlign.Center,
)
}
Tab(
selected = selectedTab == 1,
onClick = { selectedTab = 1 }
) {
Text(
text = "Аренда велосипедов",
modifier = Modifier.padding(8.dp),
textAlign = TextAlign.Center,
)
}
Tab(
selected = selectedTab == 2,
onClick = { selectedTab = 2 }
) {
Text(
text = "Контакты разработчиков",
modifier = Modifier
.padding(8.dp),
textAlign = TextAlign.Center,
)
}
}
Spacer(modifier = Modifier.height(16.dp))
when (selectedTab) {
0 -> InfoUniver()
1 -> InfoBike()
2 -> InfoProgrammer()
}
}
}
}
@Composable
fun InfoUniver() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 65.dp),
contentPadding = PaddingValues(16.dp)
) {
item {
Text(
text = "Основные сведения",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(16.dp))
Text(
"Полное наименование образовательной организации",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "Федеральное государственное бюджетное образовательное учреждение высшего образования \"Ульяновский государственный технический университет\""
)
Spacer(modifier = Modifier.height(8.dp))
Text(
"Сокращенное (при наличии) наименование образовательной организации",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "УлГТУ, Ульяновский государственный технический университет")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Дата создания образовательной организации",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "18.09.1957")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Адрес местонахождения образовательной организации",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "432027, Ульяновская область, г. Ульяновск, улица Северный Венец, дом 32")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Филиалы образовательной организации",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "Имеются")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Представительства образовательной организации",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "Имеются")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Режим, график работы",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "Понедельник-четверг - 8.30 - 17.22")
Text(text = "Пятница- 8.30 - 16.22")
Text(text = "Перерыв 13.00 до 13.40")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Контактные телефоны",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "8(8422)43-02-37")
Spacer(modifier = Modifier.height(8.dp))
Text(
"Адреса электронной почты",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Text(text = "rector@ulstu.ru")
}
}
}
@Composable
fun InfoBike() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 65.dp),
contentPadding = PaddingValues(16.dp)
) {
item {
Text(
text = "Основные сведения",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(16.dp))
Text("Технический университет подобным сокровищем обладает!",modifier = Modifier.padding(bottom = 16.dp))
Text(
text = "Аренда велосипедов стала популярной и удобной системой передвижения в нашем университете. " +
"Это новая и интересная инициатива, которая предоставляет возможность студентам и персоналу " +
"университета арендовать велосипеды для своих транспортных нужд."
,modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "Система аренды велосипедов в университете работает через специальное мобильное приложение, " +
"которое позволяет зарегистрированным пользователям легко и быстро арендовать велосипеды. " +
"Велосипеды предоставляются в зоне университета и пользуются большим спросом среди студентов, " +
"которые хотят быстро и удобно перемещаться по территорий и её окрестности."
,modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "В целом, система аренды велосипедов в университете представляет собой инновационный и удобный способ транспортировки. " +
"Она делает перемещение по университету более доступным, эффективным и приятным. " +
"Кроме того, аренда велосипедов способствует здоровому образу жизни и позволяет студентам" +
" в полной мере наслаждаться красотами университетского окружения.",
modifier = Modifier.padding(bottom = 16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text("Адреса электронной почты",
fontSize = 16.sp,
fontWeight = FontWeight.Bold)
Text(text = "arendabike@ulstu.ru")
}
}
}
@Composable
fun InfoProgrammer() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 65.dp),
contentPadding = PaddingValues(16.dp)
) {
item {
Text(
text = "Основные сведения",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Box(
modifier = Modifier
.size(200.dp)
.clip(shape = RoundedCornerShape(8.dp))
) {
Image(
painter = painterResource(id = R.drawable.kashinmaksim),
contentDescription = "Maxim",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
Spacer(modifier = Modifier.height(16.dp))
Text("Подробная информация",
fontSize = 16.sp,
fontWeight = FontWeight.Bold)
Text(
text = "Кашин Максим Игоревич"
)
Text("Личная информация",
fontSize = 16.sp,
fontWeight = FontWeight.Bold)
Text(
text = "Родной город: Сыктывкар"
)
Text(
text = "Языки: Русский"
)
Text(
text = "День рождения: 23 декабря"
)
Text("Место работы",
fontSize = 16.sp,
fontWeight = FontWeight.Bold)
Text(
text = "ДЦТ ЛАОП"
)
Text("Образование",
fontSize = 16.sp,
fontWeight = FontWeight.Bold)
Text(
text = "Вуз: УлГТУ"
)
Text(
text = "Факультет: Факультет информационных систем и технологий"
)
Text(
text = "Специальность: Программная инженерия"
)
Text(
text = "Школа: Школа № 7 Димитровград до 2021 г."
)
}
}
}

View File

@ -1,247 +0,0 @@
package com.example.labwork.pages.product
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.models.Bicycle
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.BicycleViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@Composable
fun FormNewProduct(
bicycleViewModel: BicycleViewModel,
navHostController: NavHostController
) {
val isFormVisible = remember { mutableStateOf(false) }
var brand by remember { mutableStateOf("") }
var model by remember { mutableStateOf("") }
var color by remember { mutableStateOf("") }
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
// Установите значение состояния открытия формы как true
isFormVisible.value = true
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Добавить",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
if (isFormVisible.value) {
Column(
modifier = Modifier.fillMaxWidth()
) {
// Поле ввода для бренда
TextField(
value = brand,
onValueChange = { brand = it },
placeholder = { Text("Бренд") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
// Поле ввода для модели
TextField(
value = model,
onValueChange = { model = it },
placeholder = { Text("Модель") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
// Поле ввода для цвета
TextField(
value = color,
onValueChange = { color = it },
placeholder = { Text("Цвет") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
val newBicycle = Bicycle(null, brand, model, color, null)
bicycleViewModel.insertBicycle(newBicycle)
isFormVisible.value = false
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Принять",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
isFormVisible.value = false
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Отменить",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
}
}
}
@Composable
fun FormUpdateProduct(
item: Bicycle,
bicycleViewModel: BicycleViewModel,
navHostController: NavHostController
) {
val isFormVisible = remember { mutableStateOf(false) }
var brand by remember { mutableStateOf(item.brand) }
var model by remember { mutableStateOf(item.model) }
var color by remember { mutableStateOf(item.color) }
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.padding(start = 9.dp, bottom = 9.dp)
.size(height = 32.dp, width = 128.dp),
onClick = {
// Установите значение состояния открытия формы как true
isFormVisible.value = true
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Редактировать",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
if (isFormVisible.value) {
Column(
modifier = Modifier.fillMaxWidth()
) {
// Поле ввода для бренда
TextField(
value = brand,
onValueChange = { brand = it },
placeholder = { Text("Бренд") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
// Поле ввода для модели
TextField(
value = model,
onValueChange = { model = it },
placeholder = { Text("Модель") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
// Поле ввода для цвета
TextField(
value = color,
onValueChange = { color = it },
placeholder = { Text("Цвет") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
val newBicycle = Bicycle(item.id, brand, model, color, null)
bicycleViewModel.updateBicycle(newBicycle)
isFormVisible.value = false
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Принять",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
isFormVisible.value = false
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Отменить",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
}
}
}

View File

@ -1,170 +0,0 @@
package com.example.labwork.pages.product
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.expandIn
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.Image
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.example.labwork.R
import com.example.labwork.models.Bicycle
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.BicycleViewModel
@Composable
fun ListProduct(item: Bicycle, bicycleViewModel : BicycleViewModel, navHostController: NavHostController) {
var isFullAbout by remember { mutableStateOf(false) }
val scale by animateFloatAsState(if (isFullAbout) 1f else 0f)
val textSize by animateDpAsState(if (isFullAbout) 18.dp else 24.dp)
Card(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
shape = RoundedCornerShape(15.dp),
elevation = 5.dp
) {
Box {
Row(verticalAlignment = Alignment.CenterVertically) {
Column {
Image(
painter = painterResource(id = R.drawable.baseline_directions_bike_24),
contentDescription = "book",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(10.dp)
.size(128.dp)
.shadow(2.dp)
)
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.padding(start = 9.dp, bottom = 9.dp)
.size(height = 32.dp, width = 128.dp),
onClick = {
isFullAbout = !isFullAbout
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = if(isFullAbout) "Убрать" else "Подробнее",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center,
)
}
Column {
FormUpdateProduct(item, bicycleViewModel, navHostController)
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.padding(start = 9.dp, bottom = 9.dp)
.size(height = 32.dp, width = 128.dp),
onClick = {
bicycleViewModel.deleteBicycle(item)
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Удалить",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center,
)
}
}
}
Column {
Text(
text = item.model,
color = Color.Black,
fontSize = textSize.value.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
AnimatedVisibility(
visible = isFullAbout,
enter = fadeIn() + expandIn(),
exit = fadeOut() + shrinkOut(),
modifier = Modifier.scale(scale)
) {
Column {
Text(
color = Color.Black,
text = item.brand,
maxLines = 10,
fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
)
Text(
color = Color.Black,
text = item.color,
maxLines = 10,
fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
)
Text(
color = Color.Black,
text = item.brand,
maxLines = 10,
fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
)
Text(
color = Color.Black,
text = if (item.userId != null) "Занято" else "Свободно",
maxLines = 10,
fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
)
}
}
}
}
}
}
}

View File

@ -1,167 +0,0 @@
package com.example.labwork.pages.user
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import com.example.labwork.R
import com.example.labwork.models.User
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@Composable
fun LoginPage(
navController: NavController,
navHostController: NavHostController,
userViewModel: UserViewModel,
bicycleViewModel: BicycleViewModel
) {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var showPassword by remember { mutableStateOf(false) }
var user by remember { mutableStateOf<User?>(null) }
var acceptLogin by remember { mutableStateOf(false) }
if (acceptLogin == true) {
user?.let { userProfile ->
ProfileForm(item = userProfile, userViewModel = userViewModel, navHostController = navHostController, bicycleViewModel = bicycleViewModel)
}
}
else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(R.drawable.logo_ulstu),
contentDescription = "Logo",
modifier = Modifier.size(200.dp)
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Почта") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textStyle = TextStyle(fontSize = 16.sp),
colors = TextFieldDefaults.textFieldColors(
cursorColor = LightBluePolitech,
backgroundColor = Color.White,
textColor = LightBluePolitech,
unfocusedLabelColor = LightBluePolitech,
focusedIndicatorColor = LightBluePolitech,
unfocusedIndicatorColor = LightBluePolitech,
focusedLabelColor = LightBluePolitech
),
singleLine = true
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Пароль") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textStyle = TextStyle(fontSize = 16.sp),
colors = TextFieldDefaults.textFieldColors(
cursorColor = LightBluePolitech,
backgroundColor = Color.White,
textColor = LightBluePolitech,
unfocusedLabelColor = LightBluePolitech,
focusedIndicatorColor = LightBluePolitech,
unfocusedIndicatorColor = LightBluePolitech,
focusedLabelColor = LightBluePolitech
),
singleLine = true,
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(
onClick = { showPassword = !showPassword }
) {
Image(
painter = if (showPassword) painterResource(R.drawable.baseline_visibility) else painterResource(
R.drawable.baseline_visibility_off
),
contentDescription = "LogoVissable",
modifier = Modifier.size(24.dp)
)
}
}
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
GlobalScope.launch {
val result = userViewModel.login(email, password)
user = userViewModel.getUserByEmail(email)
if (result) {
acceptLogin = true
}
}
},
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech,),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(text = "Авторизоваться", color = Color.White)
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { navController.navigate("register") },
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(text = "Регистрация", color = Color.White)
}
}
}
}

View File

@ -1,156 +0,0 @@
package com.example.labwork.pages.user
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.DAO.UserDao
import com.example.labwork.models.Bicycle
import com.example.labwork.models.User
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@Composable
fun ProfileForm(item: User, userViewModel: UserViewModel, navHostController: NavHostController, bicycleViewModel: BicycleViewModel) {
val bicycles by bicycleViewModel.bicycles.collectAsState(emptyList())
var selectedBicycleId by remember { mutableStateOf<Int?>(null) }
val selectedBicycle = bicycles.find { it.id == selectedBicycleId }
val expandedDropdown = remember { mutableStateOf(false) }
var email by remember { mutableStateOf(item.email) }
var name by remember { mutableStateOf(item.name) }
var password by remember { mutableStateOf(item.password) }
Column(
modifier = Modifier.fillMaxWidth()
) {
TextField(
value = email,
onValueChange = { email = it },
placeholder = { Text("Почта") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
TextField(
value = name,
onValueChange = { name = it },
placeholder = { Text("Имя") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
TextField(
value = password,
onValueChange = { password = it },
placeholder = { Text("Пароль") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
Button(
onClick = { expandedDropdown.value = true },
modifier = Modifier.padding(9.dp),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
) {
Text(
text = AnnotatedString(selectedBicycle?.brand ?: "Выберите велосипед"),
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
DropdownMenu(
expanded = expandedDropdown.value,
onDismissRequest = { expandedDropdown.value = false }
) {
bicycles.forEach { bicycle ->
DropdownMenuItem(
onClick = {
selectedBicycleId = bicycle.id
expandedDropdown.value = false
println(item.id)
println(bicycle.id)
}
) {
Text(text = "${bicycle.brand} ${bicycle.model}")
}
}
}
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
userViewModel.updateUser(User(item.id!!, email, name, password))
//userViewModel.updateBicycleUserId(selectedBicycleId!!, item.id) // Обновление userId для Bicycle
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Принять",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(9.dp),
onClick = {
navHostController.navigate("ListProduct")
},
shape = RoundedCornerShape(15.dp)
) {
Text(
text = "Отменить",
color = Color.White,
fontSize = 10.sp,
textAlign = TextAlign.Center
)
}
}
}

View File

@ -1,57 +0,0 @@
package com.example.labwork.pages.user
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.labwork.R
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.DAO.UserDao
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.BicycleViewModel
import com.example.labwork.viewmodel.UserViewModel
@Composable
fun RegisteryOrLogin(userViewModel: UserViewModel, navHostController: NavHostController, bicycleViewModel: BicycleViewModel) {
val navController = rememberNavController()
NavHost(navController, startDestination = "login") {
composable("login") {
LoginPage(navController = navController, userViewModel = userViewModel, navHostController = navHostController, bicycleViewModel = bicycleViewModel)
}
composable("register") {
RegisteryPage(navController = navController, userViewModel = userViewModel, navHostController = navHostController)
}
}
}

View File

@ -1,165 +0,0 @@
package com.example.labwork.pages.user
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import com.example.labwork.R
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.DAO.UserDao
import com.example.labwork.models.Bicycle
import com.example.labwork.models.User
import com.example.labwork.ui.theme.LightBluePolitech
import com.example.labwork.viewmodel.UserViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@Composable
fun RegisteryPage(navController: NavController, userViewModel: UserViewModel, navHostController: NavHostController) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(R.drawable.logo_ulstu),
contentDescription = "Logo",
modifier = Modifier.size(200.dp)
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = username,
onValueChange = { username = it },
label = { Text("Логин") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textStyle = TextStyle(fontSize = 16.sp),
colors = TextFieldDefaults.textFieldColors(
cursorColor = LightBluePolitech,
backgroundColor = Color.White,
textColor = LightBluePolitech,
unfocusedLabelColor = LightBluePolitech,
focusedIndicatorColor = LightBluePolitech,
unfocusedIndicatorColor = LightBluePolitech,
focusedLabelColor = LightBluePolitech
),
singleLine = true
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Почта") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textStyle = TextStyle(fontSize = 16.sp),
colors = TextFieldDefaults.textFieldColors(
cursorColor = LightBluePolitech,
backgroundColor = Color.White,
textColor = LightBluePolitech,
unfocusedLabelColor = LightBluePolitech,
focusedIndicatorColor = LightBluePolitech,
unfocusedIndicatorColor = LightBluePolitech,
focusedLabelColor = LightBluePolitech
),
singleLine = true
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Пароль") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textStyle = TextStyle(fontSize = 16.sp),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.White,
textColor = LightBluePolitech,
unfocusedLabelColor = LightBluePolitech,
focusedIndicatorColor = LightBluePolitech,
unfocusedIndicatorColor = LightBluePolitech,
focusedLabelColor = LightBluePolitech
),
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = confirmPassword,
onValueChange = { confirmPassword = it },
label = { Text("Подтвердите пароль") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textStyle = TextStyle(fontSize = 16.sp),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.White,
textColor = LightBluePolitech,
unfocusedLabelColor = LightBluePolitech,
focusedIndicatorColor = LightBluePolitech,
unfocusedIndicatorColor = LightBluePolitech,
focusedLabelColor = LightBluePolitech
),
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
if (password == confirmPassword) {
val newUser = User(null, name = username, email = email, password = password)
userViewModel.insertUser(newUser)
navController.navigate("login")
}
},
colors = ButtonDefaults.buttonColors(backgroundColor = LightBluePolitech),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(text = "Зарегистрироваться", color = Color.White)
}
Spacer(modifier = Modifier.height(8.dp))
}
}

View File

@ -1,15 +0,0 @@
package com.example.labwork.repository
import androidx.paging.PagingData
import com.example.labwork.models.Bicycle
import kotlinx.coroutines.flow.Flow
interface BicycleRepository {
suspend fun insertBicycle(bicycle: Bicycle)
suspend fun updateBicycle(bicycle: Bicycle)
suspend fun deleteBicycle(bicycle: Bicycle)
suspend fun getAllBicycles(): List<Bicycle>
fun getBicycles(): Flow<PagingData<Bicycle>>
suspend fun getBicyclesByUserId(userId: Int): List<Bicycle>
suspend fun getBicycleById(bicycleId: Int?): Bicycle
}

View File

@ -1,44 +0,0 @@
package com.example.labwork.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.Paging.BicyclePagingSource
import com.example.labwork.models.Bicycle
import kotlinx.coroutines.flow.Flow
class OfflineBicycleRepository(private val bicycleDao: BicycleDao) : BicycleRepository {
override suspend fun insertBicycle(bicycle: Bicycle) {
bicycleDao.insertBicycle(bicycle)
}
override suspend fun updateBicycle(bicycle: Bicycle) {
bicycleDao.updateBicycle(bicycle)
}
override suspend fun deleteBicycle(bicycle: Bicycle) {
bicycleDao.deleteBicycle(bicycle)
}
override suspend fun getAllBicycles(): List<Bicycle> {
return bicycleDao.getAllBicycles()
}
override suspend fun getBicyclesByUserId(userId: Int): List<Bicycle> {
return bicycleDao.getBicyclesByUserId(userId)
}
override suspend fun getBicycleById(bicycleId: Int?): Bicycle {
return bicycleDao.getBicycleById(bicycleId)
}
override fun getBicycles(): Flow<PagingData<Bicycle>> = Pager(config = PagingConfig(pageSize = 3, jumpThreshold = 3, initialLoadSize = 3) )
{
BicyclePagingSource(bicycleDao)
}.flow
}

View File

@ -1,38 +0,0 @@
package com.example.labwork.repository
import com.example.labwork.database.DAO.BicycleDao
import com.example.labwork.database.DAO.UserDao
import com.example.labwork.models.Bicycle
import com.example.labwork.models.User
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override suspend fun getAllUsers(): List<User> {
return userDao.getAllUsers()
}
override suspend fun insertUser(user: User) {
userDao.insertUser(user)
}
override suspend fun updateUser(user: User) {
userDao.updateUser(user)
}
override suspend fun deleteUser(user: User) {
userDao.deleteUser(user)
}
override suspend fun getUserById(userId: Int): User {
return userDao.getUserById(userId)
}
override suspend fun getUserByEmail(email: String): User {
return userDao.getUserByEmail(email)
}
override suspend fun getUserByEmailAndPassword(email: String, password: String): User? {
return userDao.getUserByEmailAndPassword(email, password)
}
}

View File

@ -1,13 +0,0 @@
package com.example.labwork.repository
import com.example.labwork.models.User
interface UserRepository {
suspend fun getAllUsers(): List<User>
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
suspend fun getUserById(userId: Int): User
suspend fun getUserByEmail(email: String): User
suspend fun getUserByEmailAndPassword(email: String, password: String): User?
}

View File

@ -1,14 +0,0 @@
package com.example.labwork.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val LightBluePolitech = Color(0xFF0158AF)
val DarkBluePolitech = Color(0xFF123E6D)

View File

@ -1,25 +0,0 @@
package com.example.labwork.viewmodel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.labwork.BicycleRentApplication
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
BicycleScreenViewModel(BicycleRentApplication().container.bicycleRepository)
}
initializer {
BicycleViewModel(BicycleRentApplication().container.bicycleRepository)
}
initializer {
UserViewModel(BicycleRentApplication().container.userRepository)
}
}
}
fun CreationExtras.BicycleRentApplication(): BicycleRentApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as BicycleRentApplication)

View File

@ -1,11 +0,0 @@
package com.example.labwork.viewmodel
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.example.labwork.models.Bicycle
import com.example.labwork.repository.BicycleRepository
import kotlinx.coroutines.flow.Flow
class BicycleScreenViewModel(private val bicycleRepository: BicycleRepository) : ViewModel() {
val bicycles: Flow<PagingData<Bicycle>> = bicycleRepository.getBicycles()
}

View File

@ -1,69 +0,0 @@
package com.example.labwork.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.labwork.models.Bicycle
import com.example.labwork.repository.BicycleRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class BicycleViewModel(private val bicycleRepository: BicycleRepository) : ViewModel() {
private val _bicycles = MutableLiveData<List<Bicycle>>()
val bicycles: StateFlow<List<Bicycle>> = MutableStateFlow(emptyList())
private val _currentPage = MutableStateFlow(1)
val currentPage: StateFlow<Int> = _currentPage
fun setCurrentPage(page: Int) {
_currentPage.value = page
}
fun getCurrentPage(): Int {
return _currentPage.value
}
fun fetchBicycles() {
viewModelScope.launch {
val fetchedBicycles = bicycleRepository.getAllBicycles()
(bicycles as MutableStateFlow).value = fetchedBicycles
}
}
fun fetchAllBicycles() {
viewModelScope.launch {
_bicycles.value = bicycleRepository.getAllBicycles()
}
}
fun getAllBicycles() {
viewModelScope.launch {
_bicycles.value = bicycleRepository.getAllBicycles()
}
}
fun getBicycleById(bicycleId: Int) {
viewModelScope.launch {
val bicycle = bicycleRepository.getBicycleById(bicycleId)
}
}
fun insertBicycle(bicycle: Bicycle) {
viewModelScope.launch {
bicycleRepository.insertBicycle(bicycle)
}
}
fun updateBicycle(bicycle: Bicycle) {
viewModelScope.launch {
bicycleRepository.updateBicycle(bicycle)
}
}
fun deleteBicycle(bicycle: Bicycle) {
viewModelScope.launch {
bicycleRepository.deleteBicycle(bicycle)
}
}
}

View File

@ -1,57 +0,0 @@
package com.example.labwork.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.labwork.models.User
import com.example.labwork.repository.UserRepository
import kotlinx.coroutines.launch
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> get() = _users
fun getAllUsers() {
viewModelScope.launch {
_users.value = userRepository.getAllUsers()
}
}
fun getUserById(userId: Int) {
viewModelScope.launch {
val user = userRepository.getUserById(userId)
}
}
fun insertUser(user: User) {
viewModelScope.launch {
userRepository.insertUser(user)
}
}
fun updateUser(user: User) {
viewModelScope.launch {
userRepository.updateUser(user)
}
}
fun deleteUser(user: User) {
viewModelScope.launch {
userRepository.deleteUser(user)
}
}
suspend fun login(email: String, password: String): Boolean {
var isSuccess = false
val user = userRepository.getUserByEmail(email)
isSuccess = user != null && user.password == password
return isSuccess
}
suspend fun getUserByEmail(email: String): User? {
return userRepository.getUserByEmail(email)
}
}

View File

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

View File

@ -0,0 +1,41 @@
package com.example.myapplication
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.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.example.myapplication.composeui.navigation.MainNavbar
import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.ui.theme.PmudemoTheme
class MainComposeActivity : ComponentActivity() {
private val dataStoreManager = DataStoreManager(this)
private val isDarkTheme = mutableStateOf(true)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
application.deleteDatabase("pmy-db")
setContent {
PmudemoTheme(darkTheme = isDarkTheme.value) {
LaunchedEffect(key1 = true) {
dataStoreManager.getSettings().collect { setting ->
isDarkTheme.value = setting.isDarkTheme
}
}
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainNavbar(
isDarkTheme = isDarkTheme,
dataStoreManager = dataStoreManager
)
}
}
}
}
}

View File

@ -0,0 +1,26 @@
package com.example.myapplication.api
import androidx.room.TypeConverters
import com.example.myapplication.database.entities.model.LocalDateTimeConverter
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import org.threeten.bp.LocalDateTime
import org.threeten.bp.DateTimeUtils.toLocalDateTime
import org.threeten.bp.format.DateTimeFormatter
@Serializer(forClass = LocalDateTime::class)
object LocalDateTimeSerializer: KSerializer<LocalDateTime> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDateTime) {
encoder.encodeInt(value.year)
}
override fun deserialize(decoder: Decoder): LocalDateTime {
val year = decoder.decodeInt()
return LocalDateTime.of(year, 1, 1, 0, 0)
}
}

View File

@ -0,0 +1,165 @@
package com.example.myapplication.api
import com.example.myapplication.api.bike.BikeRemote
import com.example.myapplication.api.rent.RentRemote
import com.example.myapplication.api.item.ItemFromBikeRemote
import com.example.myapplication.api.item.ItemRemote
import com.example.myapplication.api.item.ItemWithBikeRemote
import com.example.myapplication.api.user.UserRemote
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
interface MyServerService {
@GET("rents")
suspend fun getRents(): List<RentRemote>
@GET("users/{id}")
suspend fun getUser(
@Path("id") id: Int,
): UserRemote
@GET("users")
suspend fun getUsers(): List<UserRemote>
@POST("users")
suspend fun createUser(
@Body user: UserRemote,
): UserRemote
@PUT("users/{id}")
suspend fun updateUser(
@Path("id") id: Int,
@Body user: UserRemote,
): UserRemote
@DELETE("users/{id}")
suspend fun deleteUser(
@Path("id") id: Int,
): UserRemote
@GET("bikes")
suspend fun getBikes(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<BikeRemote>
@GET("bikes/{id}")
suspend fun getBike(
@Path("id") id: Int,
): BikeRemote
@POST("bikes")
suspend fun createBike(
@Body bike: BikeRemote,
): BikeRemote
@PUT("bikes/{id}")
suspend fun updateBike(
@Path("id") id: Int,
@Body bike: BikeRemote,
): BikeRemote
@DELETE("bikes/{id}")
suspend fun deleteBike(
@Path("id") id: Int,
)
@GET("bikes/{bikeId}/items")
suspend fun getItemsForBike(
@Path("bikeId") bikeId: Int
): List<ItemFromBikeRemote>
@GET("items/{id}?_expand=bike")
suspend fun getItem(
@Path("id") id: Int,
): ItemWithBikeRemote
@POST("items")
suspend fun createItem(
@Body item: ItemRemote,
): ItemRemote
@PUT("items/{id}")
suspend fun updateItem(
@Path("id") id: Int,
@Body item: ItemRemote,
): ItemRemote
@DELETE("items/{id}")
suspend fun deleteItem(
@Path("id") id: Int,
): ItemFromBikeRemote
@GET("users/{id}")
suspend fun getUserCart(
@Path("id") id: Int,
): UserRemote
@PUT("users/{id}")
suspend fun updateUserCart(
@Path("id") id: Int,
@Body userRemote: UserRemote,
): UserRemote
@GET("rents")
suspend fun getRents(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<RentRemote>
@GET("rents/{id}")
suspend fun getRent(
@Path("id") id: Int,
): RentRemote
@POST("rents")
suspend fun createRent(
@Body bike: RentRemote,
): RentRemote
@DELETE("rents/{id}")
suspend fun deleteRent(
@Path("id") id: Int
)
@PUT("rents/{id}")
suspend fun updateRent(
@Path("id") id: Int,
@Body rentRemote: RentRemote,
): RentRemote
companion object {
private const val BASE_URL = "http://192.168.0.104:8079/"
//private const val BASE_URL = "http://10.0.2.2:8079/"
@Volatile
private var INSTANCE: MyServerService? = null
fun getInstance(): MyServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder().addInterceptor(logger).build()
val json = Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
contextual(LocalDateTimeSerializer)
}
} // Создаем экземпляр Json с ignoreUnknownKeys = true
return Retrofit.Builder().baseUrl(BASE_URL).client(client)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // Применяем конфигурацию Json
.build().create(MyServerService::class.java).also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,26 @@
package com.example.myapplication.api.bike
import com.example.myapplication.database.entities.model.Bike
import kotlinx.serialization.Serializable
@Serializable
data class BikeRemote(
val id: Int = 0,
val name: String = "",
val description: String = "",
val image: ByteArray? = null
)
fun BikeRemote.toBike(): Bike = Bike(
id,
name,
description,
image
)
fun Bike.toBikeRemote(): BikeRemote = BikeRemote(
uid,
name,
description,
image
)

View File

@ -0,0 +1,121 @@
package com.example.myapplication.api.bike
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.item.toItem
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Bike
import com.example.myapplication.database.entities.repository.OfflineBikeRepository
import com.example.myapplication.database.entities.repository.OfflineItemRepository
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
import com.example.myapplication.database.remotekeys.model.RemoteKeys
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class BikeRemoteMediator(
private val service: MyServerService,
private val dbBikeRepository: OfflineBikeRepository,
private val dbItemRepository: OfflineItemRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Bike>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Bike>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val bikes = service.getBikes(page, state.config.pageSize).map { it.toBike() }
val bikesWithItems = bikes.map { bike ->
service.getItemsForBike(bike.uid).map {
service.getItem(it.id).toItem()
}
}
val endOfPaginationReached = bikes.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CINEMA)
dbItemRepository.clearItems()
dbBikeRepository.clearBikes()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = bikes.map {
RemoteKeys(
entityId = it.uid,
type = RemoteKeyType.CINEMA,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbBikeRepository.insertBikes(bikes)
bikesWithItems.forEach {
try {
dbItemRepository.insertItems(it)
} catch (_: Exception) {
}
}
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Bike>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { bike ->
dbRemoteKeyRepository.getAllRemoteKeys(bike.uid, RemoteKeyType.CINEMA)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Bike>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { bike ->
dbRemoteKeyRepository.getAllRemoteKeys(bike.uid, RemoteKeyType.CINEMA)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Bike>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { bikeUid ->
dbRemoteKeyRepository.getAllRemoteKeys(bikeUid, RemoteKeyType.CINEMA)
}
}
}
}

View File

@ -0,0 +1,88 @@
package com.example.myapplication.api.bike
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Bike
import com.example.myapplication.database.entities.model.BikeWithItems
import com.example.myapplication.database.entities.model.ItemFromBike
import com.example.myapplication.database.entities.repository.BikeRepository
import com.example.myapplication.database.entities.repository.OfflineBikeRepository
import com.example.myapplication.database.entities.repository.OfflineItemRepository
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestBikeRepository(
private val service: MyServerService,
private val dbBikeRepository: OfflineBikeRepository,
private val dbItemRepository: OfflineItemRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : BikeRepository {
override fun getAllBikes(): Flow<PagingData<Bike>> {
Log.d(RestBikeRepository::class.simpleName, "Get bikes")
val pagingSourceFactory = { dbBikeRepository.getAllBikesPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = BikeRemoteMediator(
service,
dbBikeRepository,
dbItemRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getBike(uid: Int): BikeWithItems {
val bike = service.getBike(uid).toBike()
val items = service.getItemsForBike(uid).map { x ->
ItemFromBike(
x.id,
x.dateTime,
x.weight,
x.maxCount - service.getRents().flatMap { rent ->
rent.items.filter { item -> item.id == x.id }
}.sumOf { item -> item.count },
uid
)
}
return BikeWithItems(bike, items)
}
override suspend fun insertBike(bike: Bike) {
service.createBike(bike.toBikeRemote()).toBike()
}
override suspend fun updateBike(bike: Bike) {
service.updateBike(bike.uid, bike.toBikeRemote()).toBike()
}
override suspend fun deleteBike(bike: Bike) {
val cart = service.getUsers()
cart.forEach { userRemote ->
userRemote.items = userRemote.items.filter { x -> x.bikeId != bike.uid }
userRemote.id?.let { service.updateUserCart(it, userRemote) }
}
val rents = service.getRents()
rents.forEach { rentRemote ->
rentRemote.items = rentRemote.items.filter { x -> x.bikeId != bike.uid }
service.updateRent(rentRemote.id, rentRemote)
}
service.deleteBike(bike.uid)
dbBikeRepository.deleteBike(bike)
}
}

View File

@ -0,0 +1,57 @@
package com.example.myapplication.api.item
/*
@Serializable
data class BikeWithItemsRemote(
val id: Int = 0,
val name: String = "",
val description: String = "",
val image: ByteArray? = null
@SerialName("items")
val items: List<ItemFromBikeRemote>,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BikeWithItemsRemote
if (id != other.id) return false
if (name != other.name) return false
if (description != other.description) return false
if (image != null) {
if (other.image == null) return false
if (!image.contentEquals(other.image)) return false
} else if (other.image != null) return false
if (items != other.items) return false
return true
}
override fun hashCode(): Int {
var result = id
result = 31 * result + name.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + (image?.contentHashCode() ?: 0)
result = 31 * result + items.hashCode()
return result
}
}
fun BikeWithItemsRemote.toBikeWithItems(): BikeWithItems = BikeWithItems(
Bike(
id,
name,
description,
image
),
items.map { x -> x.toItemFromBike() }
)
fun Bike.toBikeWithItemsRemote(): BikeWithItemsRemote = BikeWithItemsRemote(
uid,
name,
description,
image,
items = emptyList()
)*/

View File

@ -0,0 +1,33 @@
package com.example.myapplication.api.item
import com.example.myapplication.database.entities.model.ItemFromBike
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
class ItemFromBikeRemote(
val id: Int = 0,
@Contextual
val dateTime: LocalDateTime = LocalDateTime.MIN,
val weight: Double = 0.0,
val maxCount: Int = 0,
val availableCount: Int = 0,
val bikeId: Int = 0,
)
fun ItemFromBikeRemote.toItemFromBike(): ItemFromBike = ItemFromBike(
id,
dateTime,
weight,
availableCount,
bikeId
)
fun ItemFromBike.toItemFromBikeRemote(): ItemFromBikeRemote = ItemFromBikeRemote(
uid,
dateTime,
weight,
availableCount,
bikeId
)

View File

@ -0,0 +1,19 @@
package com.example.myapplication.api.item
import com.example.myapplication.api.bike.BikeRemote
import com.example.myapplication.api.bike.toBike
import com.example.myapplication.database.entities.model.ItemFromCart
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
class ItemFromCartRemote(
val id: Int = 0,
var count: Int = 0,
var bikeId: Int = 0,
)
fun ItemFromCartRemote.toItemFromCart(bike: BikeRemote, dateTime: LocalDateTime, weight: Double, availableCount: Int): ItemFromCart =
ItemFromCart(
id, dateTime, weight, availableCount, count, bike.id, bike.toBike()
)

View File

@ -0,0 +1,22 @@
package com.example.myapplication.api.item
import com.example.myapplication.api.bike.BikeRemote
import com.example.myapplication.api.bike.toBike
import com.example.myapplication.database.entities.model.ItemFromRent
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
class ItemFromRentRemote(
val id: Int = 0,
@Contextual val dateTime: LocalDateTime = LocalDateTime.MIN,
val frozenWeight: Double = 0.0,
val count: Int = 0,
val bikeId: Int = 0,
)
fun ItemFromRentRemote.toItemFromRent(bike: BikeRemote): ItemFromRent =
ItemFromRent(
id, dateTime, frozenWeight, count, bikeId, bike.toBike()
)

View File

@ -0,0 +1,32 @@
package com.example.myapplication.api.item
import com.example.myapplication.database.entities.model.Item
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
data class ItemRemote(
val id: Int = 0,
@Contextual
val dateTime: LocalDateTime,
val weight: Double,
val maxCount: Int,
val bikeId: Int = 0
)
fun ItemRemote.toItem(): Item = Item(
id,
dateTime,
weight,
maxCount,
bikeId
)
fun Item.toItemRemote(): ItemRemote = ItemRemote(
uid,
dateTime,
weight,
maxCount,
bikeId
)

View File

@ -0,0 +1,26 @@
package com.example.myapplication.api.item
import com.example.myapplication.api.bike.BikeRemote
import com.example.myapplication.database.entities.model.Item
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.threeten.bp.LocalDateTime
@Serializable
data class ItemWithBikeRemote(
val id: Int = 0,
@Contextual
val dateTime: LocalDateTime,
val weight: Double,
val maxCount: Int,
val bikeId: Int = 0,
val bike: BikeRemote,
)
fun ItemWithBikeRemote.toItem(): Item = Item(
id,
dateTime,
weight,
maxCount,
bikeId
)

View File

@ -0,0 +1,51 @@
package com.example.myapplication.api.item
import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.entities.model.Item
import com.example.myapplication.database.entities.repository.OfflineRentItemRepository
import com.example.myapplication.database.entities.repository.OfflineItemRepository
import com.example.myapplication.database.entities.repository.OfflineUserItemRepository
import com.example.myapplication.database.entities.repository.ItemRepository
class RestItemRepository(
private val service: MyServerService,
private val dbItemRepository: OfflineItemRepository,
private val dbUserItemRepository: OfflineUserItemRepository,
private val dbRentItemRepository: OfflineRentItemRepository,
) : ItemRepository {
override suspend fun getItem(uid: Int): Item {
return service.getItem(uid).toItem()
}
override suspend fun insertItem(item: Item) {
dbItemRepository.insertItem(
service.createItem(item.toItemRemote()).toItem()
)
}
override suspend fun updateItem(item: Item) {
dbItemRepository.updateItem(
service.updateItem(
item.uid,
item.toItemRemote()
).toItem()
)
}
override suspend fun deleteItem(item: Item) {
val cart = service.getUsers()
cart.forEach { userRemote ->
userRemote.items = userRemote.items.filter { x -> x.id != item.uid }
userRemote.id?.let { service.updateUserCart(it, userRemote) }
}
val rents = service.getRents()
rents.forEach { rentRemote ->
rentRemote.items = rentRemote.items.filter { x -> x.id != item.uid }
service.updateRent(rentRemote.id, rentRemote)
}
service.deleteItem(item.uid)
dbUserItemRepository.deleteItemsByUid(item.uid)
dbRentItemRepository.deleteItemsByUid(item.uid)
dbItemRepository.deleteItem(item)
}
}

View File

@ -0,0 +1,18 @@
package com.example.myapplication.api.rent
import com.example.myapplication.api.item.ItemFromRentRemote
import com.example.myapplication.database.entities.model.Rent
import kotlinx.serialization.Serializable
@Serializable
data class RentRemote(
val id: Int = 0, val userId: Int = 0, var items: List<ItemFromRentRemote> = emptyList()
)
fun RentRemote.toRent(): Rent = Rent(
id, userId
)
fun Rent.toRentRemote(): RentRemote = RentRemote(
uid, userId!!, items = emptyList()
)

View File

@ -0,0 +1,106 @@
package com.example.myapplication.api.rent
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.MyServerService
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Rent
import com.example.myapplication.database.entities.repository.OfflineRentRepository
import com.example.myapplication.database.remotekeys.model.RemoteKeyType
import com.example.myapplication.database.remotekeys.model.RemoteKeys
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class RentRemoteMediator(
private val service: MyServerService,
private val dbRentRepository: OfflineRentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Rent>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Rent>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val rents = service.getRents(page, state.config.pageSize).map { it.toRent() }
val endOfPaginationReached = rents.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
dbRentRepository.clearRents()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = rents.map {
RemoteKeys(
entityId = it.uid,
type = RemoteKeyType.ORDER,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbRentRepository.insertRents(rents)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Rent>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { rent ->
dbRemoteKeyRepository.getAllRemoteKeys(rent.uid, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Rent>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { rent ->
dbRemoteKeyRepository.getAllRemoteKeys(rent.uid, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Rent>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { rentUid ->
dbRemoteKeyRepository.getAllRemoteKeys(rentUid, RemoteKeyType.ORDER)
}
}
}
}

View File

@ -0,0 +1,77 @@
package com.example.myapplication.api.rent
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.bike.toBikeRemote
import com.example.myapplication.api.item.toItemFromRent
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.entities.model.Rent
import com.example.myapplication.database.entities.model.RentItemCrossRef
import com.example.myapplication.database.entities.model.ItemFromRent
import com.example.myapplication.database.entities.repository.OfflineBikeRepository
import com.example.myapplication.database.entities.repository.OfflineRentRepository
import com.example.myapplication.database.entities.repository.OfflineRentItemRepository
import com.example.myapplication.database.entities.repository.RentRepository
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestRentRepository(
private val service: MyServerService,
private val dbRentRepository: OfflineRentRepository,
private val dbBikeRepository: OfflineBikeRepository,
private val dbRentItemRepository: OfflineRentItemRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RentRepository {
override fun getAllRents(userId: Int?): Flow<PagingData<Rent>> {
Log.d(RestRentRepository::class.simpleName, "Get rents")
val pagingSourceFactory = { dbRentRepository.getAllRentsPagingSource(userId) }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = RentRemoteMediator(
service,
dbRentRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getRent(uid: Int): List<ItemFromRent> {
val rent = service.getRent(uid)
dbRentItemRepository.deleteRentItems(uid)
rent.items.map {
dbRentItemRepository.insertRentItem(
RentItemCrossRef(
uid,
it.id,
it.frozenWeight,
it.count
)
)
}
return rent.items.map { x -> x.toItemFromRent(dbBikeRepository.getBike(x.bikeId).bike.toBikeRemote()) }
}
override suspend fun insertRent(rent: Rent): Long {
return dbRentRepository.insertRent(service.createRent(rent.toRentRemote()).toRent())
}
override suspend fun deleteRent(rent: Rent) {
service.deleteRent(rent.uid)
dbRentRepository.deleteRent(rent)
}
}

View File

@ -0,0 +1,39 @@
package com.example.myapplication.api.rentitem
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.item.ItemFromRentRemote
import com.example.myapplication.api.item.toItem
import com.example.myapplication.database.entities.model.RentItemCrossRef
import com.example.myapplication.database.entities.repository.OfflineRentItemRepository
import com.example.myapplication.database.entities.repository.RentItemRepository
class RestRentItemRepository(
private val service: MyServerService,
private val dbRentItemRepository: OfflineRentItemRepository
) : RentItemRepository {
override suspend fun insertRentItem(rentItemCrossRef: RentItemCrossRef) {
var rentRemote = service.getRent(rentItemCrossRef.rentId)
val item = service.getItem(rentItemCrossRef.itemId).toItem()
val itemFromRent = ItemFromRentRemote(
item.uid,
item.dateTime,
item.weight,
rentItemCrossRef.count,
item.bikeId
)
val updatedItems = rentRemote.items.toMutableList()
updatedItems.add(itemFromRent)
rentRemote = rentRemote.copy(items = updatedItems)
service.updateRent(rentItemCrossRef.rentId, rentRemote)
dbRentItemRepository.insertRentItem(rentItemCrossRef)
}
override suspend fun updateRentItem(rentItemCrossRef: RentItemCrossRef) {
}
override suspend fun deleteRentItem(rentItemCrossRef: RentItemCrossRef) {
}
}

View File

@ -0,0 +1,76 @@
package com.example.myapplication.api.user
import android.util.Log
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.item.toItemFromCart
import com.example.myapplication.database.entities.model.ItemFromCart
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.model.UserItemCrossRef
import com.example.myapplication.database.entities.repository.OfflineUserRepository
import com.example.myapplication.database.entities.repository.OfflineUserItemRepository
import com.example.myapplication.database.entities.repository.UserRepository
import kotlinx.coroutines.flow.Flow
class RestUserRepository(
private val service: MyServerService,
private val dbUserRepository: OfflineUserRepository,
private val dbUserItemRepository: OfflineUserItemRepository,
) : UserRepository {
override suspend fun getAllUsers(): List<User> {
val existUsers = dbUserRepository.getAllUsers().associateBy { it.uid }.toMutableMap()
service.getUsers()
.map { it.toUser() }
.forEach { user ->
val existUser = existUsers[user.uid]
if (existUser == null) {
dbUserRepository.insertUser(user)
} else if (existUser != user) {
dbUserRepository.updateUser(user)
}
existUsers[user.uid] = user
}
return existUsers.map { it.value }.sortedBy { it.uid }
}
override suspend fun getCartByUser(userId: Int): List<ItemFromCart> {
val cart = service.getUserCart(userId)
dbUserItemRepository.deleteUserItems(userId)
cart.items.map { itemFromCartRemote ->
dbUserItemRepository.insertUserItem(
UserItemCrossRef(
userId,
itemFromCartRemote.id,
itemFromCartRemote.count
)
)
}
return cart.items.map {
val item = service.getItem(it.id)
it.toItemFromCart(
item.bike,
item.dateTime,
item.weight,
item.maxCount - service.getRents().flatMap { rent ->
rent.items.filter { item -> item.id == it.id }
}.sumOf { item -> item.count })
}
}
override suspend fun insertUser(user: User) {
service.createUser(user.toUserRemote()).toUser()
}
override suspend fun updateUser(user: User) {
user.uid?.let { service.updateUser(it, user.toUserRemote()).toUser() }
}
override suspend fun deleteUser(user: User) {
user.uid?.let { service.deleteUser(it).toUser() }
}
override suspend fun getUserById(idUser: Int?): User? =
idUser?.let { service.getUser(it).toUser() }
}

View File

@ -0,0 +1,25 @@
package com.example.myapplication.api.user
import com.example.myapplication.api.item.ItemFromCartRemote
import com.example.myapplication.database.entities.model.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int? = 0,
val login: String = "",
val password: String = "",
var items: List<ItemFromCartRemote> = emptyList()
)
fun UserRemote.toUser(): User = User(
id,
login,
password
)
fun User.toUserRemote(): UserRemote = UserRemote(
uid,
login,
password
)

View File

@ -0,0 +1,62 @@
package com.example.myapplication.api.useritem
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.item.ItemFromCartRemote
import com.example.myapplication.api.item.toItem
import com.example.myapplication.database.entities.model.UserItemCrossRef
import com.example.myapplication.database.entities.repository.OfflineUserItemRepository
import com.example.myapplication.database.entities.repository.UserItemRepository
class RestUserItemRepository(
private val service: MyServerService,
private val dbUserItemRepository: OfflineUserItemRepository
) : UserItemRepository {
override suspend fun insertUserItem(userItemCrossRef: UserItemCrossRef) {
var cartItems = service.getUserCart(userItemCrossRef.userId)
cartItems.items.forEach { item ->
if (item.id == userItemCrossRef.itemId)
return
}
val item = service.getItem(userItemCrossRef.itemId).toItem()
val itemFromCart = ItemFromCartRemote(
item.uid,
userItemCrossRef.count,
item.bikeId,
)
val updatedItems = cartItems.items.toMutableList()
updatedItems.add(itemFromCart)
cartItems = cartItems.copy(items = updatedItems)
service.updateUserCart(userItemCrossRef.userId, cartItems)
dbUserItemRepository.insertUserItem(userItemCrossRef)
}
override suspend fun updateUserItem(userItemCrossRef: UserItemCrossRef) {
val userRemote = service.getUserCart(userItemCrossRef.userId)
if (userItemCrossRef.count <= 0) {
userRemote.items =
userRemote.items.filter { x -> x.id != userItemCrossRef.itemId }
} else
userRemote.items.forEach {
if (it.id == userItemCrossRef.itemId) {
it.count = userItemCrossRef.count
}
}
service.updateUserCart(userItemCrossRef.userId, userRemote)
dbUserItemRepository.updateUserItem(userItemCrossRef)
}
override suspend fun deleteUserItem(userItemCrossRef: UserItemCrossRef) {
updateUserItem(userItemCrossRef)
dbUserItemRepository.deleteUserItem(userItemCrossRef)
}
override suspend fun deleteUserItems(userId: Int) {
val userRemote = service.getUserCart(userId)
userRemote.items = emptyList()
service.updateUserCart(userId, userRemote)
dbUserItemRepository.deleteUserItems(userId)
}
}

View File

@ -0,0 +1,318 @@
package com.example.myapplication.composeui
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.DismissValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.SwipeToDismiss
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDismissState
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.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.R
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.database.entities.composeui.CartUiState
import com.example.myapplication.database.entities.composeui.CartViewModel
import com.example.myapplication.database.entities.model.Item
import com.example.myapplication.database.entities.model.ItemFromCart
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun Cart(
viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val cartUiState = viewModel.cartUiState
LaunchedEffect(Unit) {
viewModel.refreshState()
}
Cart(
cartUiState = cartUiState,
modifier = Modifier
.padding(all = 10.dp),
onSwipe = { item: ItemFromCart, user: Int ->
coroutineScope.launch {
viewModel.removeFromCart(
item = Item(
uid = item.uid,
dateTime = item.dateTime,
weight = item.weight,
maxCount = 0,
bikeId = item.bikeId
), user = user
)
}
},
onChangeCount = { item: ItemFromCart, user: Int, count: Int ->
coroutineScope.launch {
viewModel.updateFromCart(
item = Item(
uid = item.uid,
dateTime = item.dateTime,
weight = item.weight,
maxCount = 0,
bikeId = item.bikeId
), userId = user, count = count, availableCount = item.availableCount
)
}
},
onAddToRent = { items: List<ItemFromCart>, user: Int ->
coroutineScope.launch {
viewModel.addToRent(items = items, userId = user)
}
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Cart(
cartUiState: CartUiState,
modifier: Modifier,
onSwipe: (ItemFromCart, Int) -> Unit,
onChangeCount: (ItemFromCart, Int, Int) -> Unit,
onAddToRent: (List<ItemFromCart>, Int) -> Unit
) {
LazyColumn(
modifier = modifier
) {
items(cartUiState.itemList, key = { it.uid.toString() }) { item ->
val dismissState: DismissState = rememberDismissState(
positionalThreshold = { 200.dp.toPx() }
)
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
onSwipe(item, 1)
}
SwipeToDelete(
dismissState = dismissState,
item = item,
onChangeCount = onChangeCount
)
}
}
Column {
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { onAddToRent(cartUiState.itemList, 1) },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) { Text("Арендовать") }
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
item: ItemFromCart,
onChangeCount: (ItemFromCart, Int, Int) -> Unit,
) {
SwipeToDismiss(
state = dismissState,
directions = setOf(
DismissDirection.EndToStart
),
background = {
val backgroundColor by animateColorAsState(
when (dismissState.targetValue) {
DismissValue.DismissedToStart -> Color.Red.copy(alpha = 0.8f)
else -> MaterialTheme.colorScheme.background
},
label = ""
)
val iconScale by animateFloatAsState(
targetValue = if (dismissState.targetValue == DismissValue.DismissedToStart) 1.3f else 0.5f,
label = ""
)
Box(
Modifier
.fillMaxSize()
.background(color = backgroundColor)
.padding(end = 16.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
modifier = Modifier.scale(iconScale),
imageVector = Icons.Outlined.Delete,
contentDescription = "Delete",
tint = Color.White
)
}
},
dismissContent = {
ItemListItem(
item = item,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondary),
onChangeCount = onChangeCount
)
}
)
}
@Composable
private fun ItemListItem(
item: ItemFromCart,
modifier: Modifier = Modifier,
onChangeCount: (ItemFromCart, Int, Int) -> Unit,
) {
var currentCount by remember { mutableStateOf(item.count) }
val dateFormatter = DateTimeFormatter.ofPattern("yyyy")
val formattedDate = dateFormatter.format(item.dateTime)
Column {
/* Text(
text = formattedDate,
color = MaterialTheme.colorScheme.onBackground,
)*/
Box(
modifier = modifier
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (item.bike.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
item.bike.image,
0,
item.bike.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "${item.bike.name}\n" +
"Вес: ${item.weight}\n" +
"${currentCount}/${item.availableCount}\n" +
"Год: ${formattedDate}",
color = MaterialTheme.colorScheme.onSecondary
)
}
Box(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.onBackground,
shape = RoundedCornerShape(10.dp)
) // Задаем фон для кнопок
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { onChangeCount(item, 1, --currentCount) }
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.minus),
contentDescription = "Уменьшить",
tint = MaterialTheme.colorScheme.background,
modifier = Modifier.size(10.dp)
)
}
Text(
text = "$currentCount",
color = MaterialTheme.colorScheme.background
)
IconButton(
onClick = {
onChangeCount(
item,
1,
if (currentCount != item.availableCount) ++currentCount else currentCount
)
}
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Увеличить",
tint = MaterialTheme.colorScheme.background,
modifier = Modifier.size(10.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 CartPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Cart()
}
}
}

View File

@ -0,0 +1,243 @@
package com.example.myapplication.composeui.navigation
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.myapplication.composeui.Cart
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.database.entities.composeui.BikeList
import com.example.myapplication.database.entities.composeui.BikeView
import com.example.myapplication.database.entities.composeui.CurrentUserViewModel
import com.example.myapplication.database.entities.composeui.RentList
import com.example.myapplication.database.entities.composeui.RentView
import com.example.myapplication.database.entities.composeui.UserProfile
import com.example.myapplication.database.entities.composeui.edit.BikeEdit
import com.example.myapplication.database.entities.composeui.edit.ItemEdit
import com.example.myapplication.datastore.DataStoreManager
@Composable
fun Topbar(
navController: NavHostController,
currentScreen: Screen?
) {
var searchQuery by remember { mutableStateOf("") }
Box(
modifier = Modifier
.fillMaxWidth()
.background(color = MaterialTheme.colorScheme.primary)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (
navController.previousBackStackEntry != null
&& (currentScreen == null || !currentScreen.showInBottomBar)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clickable { navController.navigateUp() },
tint = MaterialTheme.colorScheme.secondary
)
} else
Icon(
imageVector = Icons.Default.Person,
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clickable { navController.navigate(Screen.UserProfile.route) },
tint = MaterialTheme.colorScheme.secondary
)
Spacer(modifier = Modifier.width(16.dp))
BasicTextField(
value = searchQuery,
onValueChange = { newValue -> searchQuery = newValue },
modifier = Modifier
.weight(1f)
.height(36.dp)
.background(
color = MaterialTheme.colorScheme.onPrimary,
RoundedCornerShape(18.dp)
)
.padding(start = 13.dp, top = 8.dp),
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = androidx.compose.ui.text.input.ImeAction.Search
),
keyboardActions = KeyboardActions(
onSearch = { }
)
)
Spacer(modifier = Modifier.width(16.dp))
Icon(
imageVector = Icons.Default.Search,
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clickable { },
tint = MaterialTheme.colorScheme.secondary
)
}
}
}
@Composable
fun Navbar(
navController: NavHostController,
currentDestination: NavDestination?,
modifier: Modifier = Modifier
) {
NavigationBar(modifier = modifier, containerColor = MaterialTheme.colorScheme.primary) {
Screen.bottomBarItems.forEach { screen ->
NavigationBarItem(
icon = {
Icon(
screen.icon,
contentDescription = null,
tint = if (currentDestination?.hierarchy?.any { it.route == screen.route } == true)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
},
label = { Text(
stringResource(screen.resourceId),
color = MaterialTheme.colorScheme.secondary
) },
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,
innerPadding: PaddingValues,
isDarkTheme: MutableState<Boolean>,
dataStore: DataStoreManager,
modifier: Modifier = Modifier,
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
NavHost(
navController,
startDestination = Screen.BikeList.route,
modifier.padding(innerPadding)
) {
composable(Screen.BikeList.route) { BikeList(navController) }
composable(Screen.RentList.route) { RentList(navController, 1) }
composable(Screen.Cart.route) { Cart() }
composable(Screen.UserProfile.route) { UserProfile(isDarkTheme, dataStore, currentUserViewModel = currentUserViewModel) }
composable(
Screen.BikeEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) {
BikeEdit(navController)
}
composable(
Screen.ItemEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType },
navArgument("bikeId") { type = NavType.IntType })
) {
ItemEdit(navController)
}
composable(
Screen.BikeView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { BikeView(navController, currentUserViewModel = currentUserViewModel) }
}
composable(
Screen.RentView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { RentView(it.getInt("id")) }
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainNavbar(
isDarkTheme: MutableState<Boolean>,
dataStoreManager: DataStoreManager
) {
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)
}
}
) { innerPadding ->
Navhost(navController, innerPadding, isDarkTheme, dataStoreManager)
}
}

View File

@ -0,0 +1,58 @@
package com.example.myapplication.composeui.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.ui.graphics.vector.ImageVector
import com.example.myapplication.R
enum class Screen(
val route: String,
@StringRes val resourceId: Int,
val icon: ImageVector = Icons.Filled.Favorite,
val showInBottomBar: Boolean = true
) {
BikeList(
"Bike-list", R.string.Bike_main_title, Icons.Filled.Home
),
BikeEdit(
"Bike-edit/{id}", R.string.Bike_view_title, showInBottomBar = false
),
ItemEdit(
"Item-edit/{id}/{bikeId}", R.string.Item_view_title, showInBottomBar = false
),
BikeView(
"Bike-view/{id}", R.string.Bike_view_title, showInBottomBar = false
),
ItemList(
"Item-list", R.string.Items_title, showInBottomBar = false
),
Cart(
"cart", R.string.Cart_title, Icons.Filled.ShoppingCart
),
RentList(
"Rent-list", R.string.Rent_title, Icons.Filled.List
),
RentView(
"Rent-view/{id}", R.string.Rent_view_title, showInBottomBar = false
),
UserProfile(
"User-profile", R.string.Profile_title, showInBottomBar = false
);
companion object {
val bottomBarItems = listOf(
BikeList,
Cart,
RentList
)
fun getItem(route: String): Screen? {
val findRoute = route.split("/").first()
return values().find { value -> value.route.startsWith(findRoute) }
}
}
}

View File

@ -0,0 +1,101 @@
package com.example.myapplication.database
import android.content.Context
import com.example.myapplication.api.MyServerService
import com.example.myapplication.api.bike.RestBikeRepository
import com.example.myapplication.api.rent.RestRentRepository
import com.example.myapplication.api.rentitem.RestRentItemRepository
import com.example.myapplication.api.item.RestItemRepository
import com.example.myapplication.api.user.RestUserRepository
import com.example.myapplication.api.useritem.RestUserItemRepository
import com.example.myapplication.database.entities.repository.OfflineBikeRepository
import com.example.myapplication.database.entities.repository.OfflineRentRepository
import com.example.myapplication.database.entities.repository.OfflineRentItemRepository
import com.example.myapplication.database.entities.repository.OfflineItemRepository
import com.example.myapplication.database.entities.repository.OfflineUserRepository
import com.example.myapplication.database.entities.repository.OfflineUserItemRepository
import com.example.myapplication.database.remotekeys.repository.OfflineRemoteKeyRepository
interface AppContainer {
val bikeRestRepository: RestBikeRepository
val itemRestRepository: RestItemRepository
val userRestRepository: RestUserRepository
val rentRestRepository: RestRentRepository
val rentItemRestRepository: RestRentItemRepository
val userItemRestRepository: RestUserItemRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
private val bikeRepository: OfflineBikeRepository by lazy {
OfflineBikeRepository(AppDatabase.getInstance(context).bikeDao())
}
private val rentRepository: OfflineRentRepository by lazy {
OfflineRentRepository(AppDatabase.getInstance(context).rentDao())
}
private val rentItemRepository: OfflineRentItemRepository by lazy {
OfflineRentItemRepository(AppDatabase.getInstance(context).rentItemCrossRefDao())
}
private val itemRepository: OfflineItemRepository by lazy {
OfflineItemRepository(AppDatabase.getInstance(context).itemDao())
}
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val userItemRepository: OfflineUserItemRepository by lazy {
OfflineUserItemRepository(AppDatabase.getInstance(context).userItemCrossRefDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
}
override val bikeRestRepository: RestBikeRepository by lazy {
RestBikeRepository(
MyServerService.getInstance(),
bikeRepository,
itemRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val itemRestRepository: RestItemRepository by lazy {
RestItemRepository(
MyServerService.getInstance(),
itemRepository,
userItemRepository,
rentItemRepository,
)
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
MyServerService.getInstance(),
userRepository,
userItemRepository,
)
}
override val rentRestRepository: RestRentRepository by lazy {
RestRentRepository(
MyServerService.getInstance(),
rentRepository,
bikeRepository,
rentItemRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val userItemRestRepository: RestUserItemRepository by lazy {
RestUserItemRepository(
MyServerService.getInstance(),
userItemRepository,
)
}
override val rentItemRestRepository: RestRentItemRepository by lazy {
RestRentItemRepository(
MyServerService.getInstance(),
rentItemRepository,
)
}
}

View File

@ -0,0 +1,194 @@
package com.example.myapplication.database
import android.content.Context
import android.graphics.Bitmap
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.database.entities.dao.BikeDao
import com.example.myapplication.database.entities.dao.RentDao
import com.example.myapplication.database.entities.dao.RentItemCrossRefDao
import com.example.myapplication.database.entities.dao.ItemDao
import com.example.myapplication.database.entities.dao.UserDao
import com.example.myapplication.database.entities.dao.UserItemCrossRefDao
import com.example.myapplication.database.entities.model.Bike
import com.example.myapplication.database.entities.model.LocalDateTimeConverter
import com.example.myapplication.database.entities.model.Rent
import com.example.myapplication.database.entities.model.RentItemCrossRef
import com.example.myapplication.database.entities.model.Item
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.model.UserItemCrossRef
import com.example.myapplication.database.remotekeys.dao.RemoteKeysDao
import com.example.myapplication.database.remotekeys.model.RemoteKeys
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.ByteArrayOutputStream
@Database(
entities = [
Bike::class,
Item::class,
Rent::class,
RentItemCrossRef::class,
User::class,
UserItemCrossRef::class,
RemoteKeys::class
],
version = 1,
exportSchema = false
)
@TypeConverters(LocalDateTimeConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun bikeDao(): BikeDao
abstract fun itemDao(): ItemDao
abstract fun rentDao(): RentDao
abstract fun rentItemCrossRefDao(): RentItemCrossRefDao
abstract fun userDao(): UserDao
abstract fun userItemCrossRefDao(): UserItemCrossRefDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "pmy-db"
@Volatile
private var INSTANCE: AppDatabase? = null
private suspend fun populateDatabase() {
INSTANCE?.let { database ->
// Users
val userDao = database.userDao()
val user1 = User(1, "login", "password")
userDao.insert(user1)
/*// Bikes
val bikeDao = database.bikeDao()
val bike1 =
Bike(1, "a", "Desc1", createColoredImage(android.graphics.Color.BLUE), 2023)
val bike2 =
Bike(2, "b", "Desc2", createColoredImage(android.graphics.Color.GREEN), 2023)
val bike3 =
Bike(3, "c", "Desc3", createColoredImage(android.graphics.Color.RED), 2023)
val bike4 =
Bike(4, "d", "Desc4", createColoredImage(android.graphics.Color.CYAN), 2023)
bikeDao.insert(bike1)
bikeDao.insert(bike2)
bikeDao.insert(bike3)
bikeDao.insert(bike4)
for (i in 5..20) {
val bike = Bike(
uid = i,
name = generateBikeName(i),
description = "Description $i",
image = createColoredImage(getRandomColorInt()),
)
bikeDao.insert(bike)
}
// Rents
val rentDao = database.rentDao()
val rent1 = Rent(1, 1)
val rent2 = Rent(2, 1)
val rent3 = Rent(3, 1)
val rent4 = Rent(4, 1)
rentDao.insert(rent1)
rentDao.insert(rent2)
rentDao.insert(rent3)
rentDao.insert(rent4)
// Items
val itemDao = database.itemDao()
val item1 = Item(1, LocalDateTime.now(), 150.0, 120, bike1.uid)
val item2 = Item(2, LocalDateTime.now(), 200.0, 110, bike2.uid)
val item3 = Item(3, LocalDateTime.now(), 300.0, 100, bike3.uid)
val item4 = Item(4, LocalDateTime.now(), 450.0, 200, bike1.uid)
itemDao.insert(item1)
itemDao.insert(item2)
itemDao.insert(item3)
itemDao.insert(item4)
// RentItemCrossRef для связи арендаов с сеансами
val rentItemCrossRefDao = database.rentItemCrossRefDao()
if (item1.uid != null && item2.uid != null && item3.uid != null) {
val rentItemCrossRef1 =
RentItemCrossRef(rent1.uid, item3.uid, 150.0, 5)
val rentItemCrossRef2 =
RentItemCrossRef(rent1.uid, item2.uid, 300.0, 10)
val rentItemCrossRef3 =
RentItemCrossRef(rent2.uid, item2.uid, 350.0, 6)
val rentItemCrossRef4 =
RentItemCrossRef(rent3.uid, item1.uid, 250.0, 10)
val rentItemCrossRef5 =
RentItemCrossRef(rent3.uid, item3.uid, 150.0, 16)
val rentItemCrossRef6 =
RentItemCrossRef(rent4.uid, item3.uid, 150.0, 2)
rentItemCrossRefDao.insert(rentItemCrossRef1)
rentItemCrossRefDao.insert(rentItemCrossRef2)
rentItemCrossRefDao.insert(rentItemCrossRef3)
rentItemCrossRefDao.insert(rentItemCrossRef4)
rentItemCrossRefDao.insert(rentItemCrossRef5)
rentItemCrossRefDao.insert(rentItemCrossRef6)
}
// UserItems
val userItemCrossRefDao = database.userItemCrossRefDao()
val userItemCrossRef1 = UserItemCrossRef(1, 1, 5)
val userItemCrossRef2 = UserItemCrossRef(1, 3, 15)
userItemCrossRefDao.insert(userItemCrossRef1)
userItemCrossRefDao.insert(userItemCrossRef2)*/
}
}
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()
}
}
})
.build()
.also { INSTANCE = it }
}
}
private fun createColoredImage(color: Int): ByteArray {
val width = 100
val height = 100
val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bmp.eraseColor(color)
val stream = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
return stream.toByteArray()
}
private fun getRandomColorInt(): Int {
val red = (0..255).random()
val green = (0..255).random()
val blue = (0..255).random()
return (0xFF shl 24) or (red shl 16) or (green shl 8) or blue
}
private fun generateBikeName(index: Int): String {
val base = 'a'.code
val alphabetSize = 26
val sb = StringBuilder()
var remainder = index
do {
val letter = (remainder % alphabetSize + base).toChar()
sb.insert(0, letter)
remainder /= alphabetSize
} while (remainder > 0)
return sb.toString()
}
}
}

View File

@ -0,0 +1,73 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.myapplication.BikeApplication
import com.example.myapplication.database.entities.composeui.edit.BikeEditViewModel
import com.example.myapplication.database.entities.composeui.edit.ItemEditViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
CurrentUserViewModel(bikeApplication().container.userRestRepository)
}
initializer {
EntryUserViewModel(bikeApplication().container.userRestRepository)
}
initializer {
RegisterUserViewModel(bikeApplication().container.userRestRepository)
}
initializer {
BikeListViewModel(bikeApplication().container.bikeRestRepository)
}
initializer {
BikeEditViewModel(
this.createSavedStateHandle(),
bikeApplication().container.bikeRestRepository
)
}
initializer {
BikeViewModel(
this.createSavedStateHandle(),
bikeApplication().container.bikeRestRepository,
)
}
initializer {
ItemListViewModel(
bikeApplication().container.itemRestRepository,
bikeApplication().container.userItemRestRepository,
)
}
initializer {
ItemEditViewModel(
this.createSavedStateHandle(),
bikeApplication().container.itemRestRepository,
)
}
initializer {
CartViewModel(
bikeApplication().container.userItemRestRepository,
bikeApplication().container.rentRestRepository,
bikeApplication().container.rentItemRestRepository,
bikeApplication().container.userRestRepository,
)
}
initializer {
RentListViewModel(
bikeApplication().container.rentRestRepository,
)
}
initializer {
RentViewModel(
this.createSavedStateHandle(),
bikeApplication().container.rentRestRepository,
)
}
}
}
fun CreationExtras.bikeApplication(): BikeApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as BikeApplication)

View File

@ -0,0 +1,234 @@
package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Bike
import kotlinx.coroutines.launch
@Composable
fun BikeList(
navController: NavController,
viewModel: BikeListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val bikePagingItems = viewModel.bikeListUiState.collectAsLazyPagingItems()
Scaffold(
topBar = {},
floatingActionButton = {
Box(
//modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center // Center align the FloatingActionButton
) {
FloatingActionButton(
onClick = {
val route = Screen.BikeEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
},
modifier = Modifier.fillMaxWidth(0.92f).align(Alignment.BottomCenter),
containerColor = MaterialTheme.colorScheme.primary
) {
Icon(
Icons.Filled.Add,
"Добавить",
tint = MaterialTheme.colorScheme.onPrimary
)
}
}
}
) { innerPadding ->
BikeList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
pagingBike = bikePagingItems,
onClick = { uid: Int ->
val route = Screen.BikeView.route.replace("{id}", uid.toString())
navController.navigate(route)
},
onDeleteClick = { bike: Bike ->
coroutineScope.launch {
viewModel.deleteBike(bike)
}
},
onEditClick = { uid: Int ->
val route = Screen.BikeEdit.route.replace("{id}", uid.toString())
navController.navigate(route)
},
)
}
}
@Composable
private fun BikeList(
modifier: Modifier = Modifier,
pagingBike: LazyPagingItems<Bike>,
onClick: (uid: Int) -> Unit,
onDeleteClick: (bike: Bike) -> Unit,
onEditClick: (bike: Int) -> Unit
) {
Column(
modifier = modifier
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
.padding(bottom = 72.dp) // Добавим отступ внизу
) {
items(pagingBike.itemCount) { index ->
val bike = pagingBike[index]
if (bike != null) {
BikeListItem(
bike = bike,
modifier = Modifier
.padding(vertical = 7.dp)
//.clickable { onClick(bike.uid) }
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(16.dp)
),
onDeleteClick = onDeleteClick,
onEditClick = onEditClick,
onClick = onClick
)
}
}
}
}
}
@Composable
private fun BikeListItem(
bike: Bike,
modifier: Modifier = Modifier,
onDeleteClick: (bike: Bike) -> Unit,
onClick: (uid: Int) -> Unit,
onEditClick: (bike: Int) -> Unit
) {
var isExpanded by remember { mutableStateOf(false) } // Состояние для определения, раскрыта ли дополнительная информация о велосипеде
Box(
modifier = modifier
.clip(RoundedCornerShape(8.dp))
//.background(Color.White)
.shadow(4.dp)
.padding(8.dp)
.clickable { isExpanded = !isExpanded } // Добавляем обработчик нажатия для раскрытия/скрытия дополнительной информации
) {
Column(Modifier.padding(8.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (bike.image != null)
Box(
modifier = Modifier
.size(90.dp)
.padding(4.dp)
.border(width = 1.dp, color = MaterialTheme.colorScheme.secondary) // Добавляем рамку
) {
Image(
bitmap = BitmapFactory.decodeByteArray(
bike.image,
0,
bike.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
Text(
"${bike.name}",
color = MaterialTheme.colorScheme.secondary,
//style = MaterialTheme.typography.h6
)
// Добавляем пустое пространство для разделения текста и кнопок
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = { onEditClick(bike.uid) },
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "Редактировать",
tint = MaterialTheme.colorScheme.secondary,
)
}
IconButton(
onClick = { onClick(bike.uid) },
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "Информация",
tint = MaterialTheme.colorScheme.secondary,
)
}
IconButton(
onClick = { onDeleteClick(bike) },
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить",
tint = MaterialTheme.colorScheme.secondary,
)
}
}
AnimatedVisibility(visible = isExpanded) { // Анимированная видимость для дополнительной информации
Text(
text = bike.description,
color = MaterialTheme.colorScheme.secondary,
//style = MaterialTheme.typography.body2,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
}

View File

@ -0,0 +1,17 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.example.myapplication.database.entities.model.Bike
import com.example.myapplication.database.entities.repository.BikeRepository
import kotlinx.coroutines.flow.Flow
class BikeListViewModel(
private val bikeRepository: BikeRepository
) : ViewModel() {
val bikeListUiState: Flow<PagingData<Bike>> = bikeRepository.getAllBikes()
suspend fun deleteBike(bike: Bike) {
bikeRepository.deleteBike(bike)
}
}

View File

@ -0,0 +1,143 @@
package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.database.entities.model.Bike
@Composable
fun BikeView(
navController: NavController,
viewModel: BikeViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val bikeUiState = viewModel.bikeUiState
LaunchedEffect(Unit) {
viewModel.refreshState()
}
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
) {
val bike: Bike? = bikeUiState.bikeWithItems?.bike
if (bike != null) {
Box(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp)
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(color = MaterialTheme.colorScheme.secondary),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "${bike.name}",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onSecondary
),
textAlign = TextAlign.Center,
modifier = Modifier
.padding(bottom = 8.dp)
)
}
if (bike.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
bike.image,
0,
bike.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(4.dp)
)
Text(
text = bike.description,
color = MaterialTheme.colorScheme.onSecondary
)
}
}
}
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Доступные",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onBackground
),
modifier = Modifier
.weight(1f) // Занимает доступное пространство
.padding(top = 8.dp, bottom = 8.dp)
)
IconButton(
onClick = {
val route = Screen.ItemEdit.route.replace("{id}", 0.toString())
.replace(
"{bikeId}",
bikeUiState.bikeWithItems?.bike?.uid.toString()
)
navController.navigate(route)
}
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Добавить сеанс",
)
}
}
if (bikeUiState.bikeWithItems != null) {
ItemList(viewModel, navController, currentUserViewModel = currentUserViewModel)
}
}
}

View File

@ -0,0 +1,45 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.example.myapplication.database.entities.model.BikeWithItems
import com.example.myapplication.database.entities.repository.BikeRepository
class BikeViewModel(
savedStateHandle: SavedStateHandle, private val bikeRepository: BikeRepository
) : ViewModel() {
private val bikeUid: Int = checkNotNull(savedStateHandle["id"])
var bikeUiState by mutableStateOf(BikeUiState())
private set
suspend fun refreshState() {
if (bikeUid > 0) {
bikeUiState = BikeUiState(bikeRepository.getBike(bikeUid))
}
}
// init {
// viewModelScope.launch {
// if (bikeUid > 0) {
// bikeUiState = BikeUiState(bikeRepository.getBike(bikeUid))
// }
// }
// }
// val bikeUiState: mutableStateOf(BikeUiState()) = bikeRepository.getBike(
// bikeUid
// ).map
// {
// BikeUiState(it)
// }.stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
// initialValue = BikeUiState()
// )
}
data class BikeUiState(val bikeWithItems: BikeWithItems? = null)

View File

@ -0,0 +1,70 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.myapplication.database.entities.model.Rent
import com.example.myapplication.database.entities.model.RentItemCrossRef
import com.example.myapplication.database.entities.model.Item
import com.example.myapplication.database.entities.model.ItemFromCart
import com.example.myapplication.database.entities.model.UserItemCrossRef
import com.example.myapplication.database.entities.repository.RentRepository
import com.example.myapplication.database.entities.repository.RentItemRepository
import com.example.myapplication.database.entities.repository.UserRepository
import com.example.myapplication.database.entities.repository.UserItemRepository
import kotlinx.coroutines.delay
class CartViewModel(
private val userItemRepository: UserItemRepository,
private val rentRepository: RentRepository,
private val rentItemRepository: RentItemRepository,
private val userRepository: UserRepository,
) : ViewModel() {
private val userUid: Int = 1
var cartUiState by mutableStateOf(CartUiState())
private set
suspend fun refreshState() {
val cart = userRepository.getCartByUser(userUid)
cartUiState = CartUiState(cart)
}
suspend fun addToRent(userId: Int, items: List<ItemFromCart>) {
if (items.isEmpty())
return
val rentId = rentRepository.insertRent(Rent(0, userId))
items.forEach { item ->
rentItemRepository.insertRentItem(
RentItemCrossRef(
rentId.toInt(),
item.uid,
item.weight,
item.count
)
)
}
userItemRepository.deleteUserItems(userId)
refreshState()
}
suspend fun removeFromCart(user: Int, item: Item, count: Int = 1) {
userItemRepository.deleteUserItem(UserItemCrossRef(user, item.uid, count))
refreshState()
}
suspend fun updateFromCart(userId: Int, item: Item, count: Int, availableCount: Int)
: Boolean {
if (count == 0) {
removeFromCart(userId, item, count)
return false
}
if (count > availableCount)
return false
userItemRepository.updateUserItem(UserItemCrossRef(userId, item.uid, count))
refreshState()
return true
}
}
data class CartUiState(val itemList: List<ItemFromCart> = listOf())

View File

@ -0,0 +1,24 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.repository.UserRepository
import kotlinx.coroutines.launch
class CurrentUserViewModel(private val userRepository: UserRepository) : ViewModel(){
val argument = mutableStateOf<String?>(null)
private val userid = mutableStateOf<Int?>(null)
var user by mutableStateOf<User?>(null)
fun setArgument(arg: String) {
argument.value = arg
userid.value = arg.toInt()
viewModelScope.launch {
user = userRepository.getUserById(userid.value)
}
}
}

View File

@ -0,0 +1,20 @@
package com.example.myapplication.database.entities.composeui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.repository.UserRepository
import kotlinx.coroutines.launch
class EntryUserViewModel(private val userRepository: UserRepository) : ViewModel() {
var userList by mutableStateOf<List<User>>(emptyList())
fun setUserList() {
viewModelScope.launch {
userList=userRepository.getAllUsers()
}
}
}

View File

@ -0,0 +1,155 @@
package com.example.myapplication.database.entities.composeui
import android.graphics.BitmapFactory
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.composeui.navigation.Screen
import kotlinx.coroutines.launch
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun ItemList(
bikeWithItemsViewModel: BikeViewModel,
navController: NavController,
viewModel: ItemListViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val bikeWithItems = bikeWithItemsViewModel.bikeUiState.bikeWithItems!!
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
Log.d("Удачи","${getUser?.uid}")
LazyColumn {
if (bikeWithItems.items.isEmpty()) {
item {
Text(
text = stringResource(R.string.Item_empty_description),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
}
} else {
items(bikeWithItems.items, key = { it.uid }) { item ->
val route = Screen.ItemEdit.route.replace(
"{id}", item.uid.toString()
).replace(
"{bikeId}", bikeWithItems.bike.uid.toString()
)
val dateFormatter = DateTimeFormatter.ofPattern("yyyy")
val formattedDate = dateFormatter.format(item.dateTime)
Column {
/* Text(
text = formattedDate,
color = MaterialTheme.colorScheme.onBackground,
)*/
Box(modifier = Modifier
.padding(vertical = 7.dp)
.clickable {
navController.navigate(route)
}
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(16.dp)
)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (bikeWithItems.bike.image != null) Image(
bitmap = BitmapFactory.decodeByteArray(
bikeWithItems.bike.image,
0,
bikeWithItems.bike.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "Вес: ${item.weight}\n" + "Количество: ${item.availableCount}\n" + "Год: ${formattedDate}",
color = MaterialTheme.colorScheme.onPrimary
)
}
IconButton(
onClick = {
coroutineScope.launch {
if (item.availableCount != 0) {
viewModel.addItemInCart(itemId = item.uid)
}
}
},
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onPrimary
)
}
IconButton(
onClick = {
coroutineScope.launch {
viewModel.deleteItem(item = item)
bikeWithItemsViewModel.refreshState()
}
},
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onPrimary
)
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import com.example.myapplication.database.entities.model.Item
import com.example.myapplication.database.entities.model.ItemFromBike
import com.example.myapplication.database.entities.model.UserItemCrossRef
import com.example.myapplication.database.entities.repository.ItemRepository
import com.example.myapplication.database.entities.repository.UserItemRepository
class ItemListViewModel(
private val itemRepository: ItemRepository,
private val userItemRepository: UserItemRepository
) : ViewModel() {
suspend fun deleteItem(item: ItemFromBike) {
itemRepository.deleteItem(
Item(
uid = item.uid,
dateTime = item.dateTime,
weight = item.weight,
maxCount = 0,
bikeId = 0
)
)
}
suspend fun addItemInCart(itemId: Int, count: Int = 1) {
try {
userItemRepository.insertUserItem(UserItemCrossRef(1, itemId, count))
} catch (_: Exception) {
}
}
}

View File

@ -0,0 +1,26 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.database.entities.repository.UserRepository
import kotlinx.coroutines.launch
class RegisterUserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> get() = _users
fun setUserList() {
viewModelScope.launch {
_users.value = userRepository.getAllUsers()
}
}
suspend fun insertUser(user: User) {
userRepository.insertUser(user)
}
}

View File

@ -0,0 +1,104 @@
package com.example.myapplication.database.entities.composeui
import android.content.res.Configuration
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.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.launch
@Composable
fun RentList(
navController: NavController?,
userId: Int?,
viewModel: RentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val rentsUiState = viewModel.rentListUiState.collectAsLazyPagingItems()
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
) {
items(
count = rentsUiState.itemCount,
key = rentsUiState.itemKey(),
contentType = rentsUiState.itemContentType()
) { index ->
val rent = rentsUiState[index]
val rentId = Screen.RentView.route.replace("{id}", rent!!.uid.toString())
Box(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.clickable { navController?.navigate(rentId) }
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(16.dp)
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Аренда №${rent.uid}", color = MaterialTheme.colorScheme.onPrimary)
IconButton(
onClick = {
coroutineScope.launch {
viewModel.deleteRent(rent!!)
}
}
) {
Icon(
Icons.Default.Delete,
tint = MaterialTheme.colorScheme.onPrimary,
contentDescription = "Удалить"
)
}
}
}
}
}
}
@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 RentListPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
RentList(navController = null, 1)
}
}
}

View File

@ -0,0 +1,19 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.example.myapplication.database.entities.model.Rent
import com.example.myapplication.database.entities.repository.RentRepository
import kotlinx.coroutines.flow.Flow
class RentListViewModel(
private val rentRepository: RentRepository
) : ViewModel() {
val rentListUiState: Flow<PagingData<Rent>> = rentRepository.getAllRents(1)
suspend fun deleteRent(rent: Rent) {
rentRepository.deleteRent(rent)
}
}
data class RentListUiState(val rentList: List<Rent> = listOf())

View File

@ -0,0 +1,132 @@
package com.example.myapplication.database.entities.composeui
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
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.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.ui.theme.PmudemoTheme
import org.threeten.bp.format.DateTimeFormatter
@Composable
fun RentView(
id: Int,
viewModel: RentViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val rentUiState by viewModel.rentUiState.collectAsState()
LazyColumn(
modifier = Modifier
.padding(10.dp)
) {
items(rentUiState.itemList) { item ->
val count = remember { mutableStateOf(item.count) }
val dateFormatter = DateTimeFormatter.ofPattern("yyyy")
val formattedDate = dateFormatter.format(item.dateTime)
/* Text(
text = formattedDate,
color = MaterialTheme.colorScheme.onBackground,
)*/
Box(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.primary)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (item.bike.image != null)
Image(
bitmap = BitmapFactory.decodeByteArray(
item.bike.image,
0,
item.bike.image.size
).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.size(90.dp)
.padding(4.dp)
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "${item.bike.name}\n" +
"Вес: ${item.frozenWeight}\n" +
"Количество: ${count.value}\n" +
"Дата: ${formattedDate}",
color = MaterialTheme.colorScheme.onPrimary
)
/* Row(verticalAlignment = Alignment.CenterVertically) {
// Кнопка "Удалить"
Button(
onClick = {
coroutineScope.launch {
//viewModel.removeItemFromRent(item)
}
},
modifier = Modifier.padding(end = 8.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить"
)
Text(text = "Удалить")
}
}*/
}
}
}
}
}
}
@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 RentViewPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
RentView(id = 1)
}
}
}

View File

@ -0,0 +1,30 @@
package com.example.myapplication.database.entities.composeui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.entities.model.ItemFromRent
import com.example.myapplication.database.entities.repository.RentRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class RentViewModel(
savedStateHandle: SavedStateHandle,
private val rentRepository: RentRepository
) : ViewModel() {
private val rentUid: Int = checkNotNull(savedStateHandle["id"])
val rentUiState: StateFlow<RentUiState> = flow{emit(rentRepository.getRent(rentUid))} .map {
RentUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppContainer.TIMEOUT),
initialValue = RentUiState()
)
}
data class RentUiState(val itemList: List<ItemFromRent> = listOf())

View File

@ -0,0 +1,230 @@
package com.example.myapplication.database.entities.composeui
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
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.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.database.entities.model.User
import com.example.myapplication.datastore.DataStoreManager
import com.example.myapplication.datastore.SettingData
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
@SuppressLint("UnrememberedMutableState")
@Composable
fun UserProfile(
isDarkTheme: MutableState<Boolean>,
dataStoreManager: DataStoreManager,
registerUserViewModel: RegisterUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
entryUserViewModel: EntryUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var isRegistration by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
registerUserViewModel.setUserList()
var users = mutableStateOf<List<User>>(emptyList())
registerUserViewModel.users.observeForever { userList ->
users.value = userList
}
entryUserViewModel.setUserList()
val users_entry = mutableStateOf<List<User>>(entryUserViewModel.userList)
LazyColumn {
item {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Логин",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
BasicTextField(
value = username,
onValueChange = { newValue -> username = newValue },
modifier = Modifier
.fillMaxWidth()
.size(36.dp)
.background(MaterialTheme.colorScheme.secondary, RoundedCornerShape(18.dp))
.padding(start = 13.dp, top = 8.dp)
)
Text(
text = "Пароль",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
BasicTextField(
value = password,
onValueChange = { newValue -> password = newValue },
modifier = Modifier
.fillMaxWidth()
.size(36.dp)
.background(MaterialTheme.colorScheme.secondary, RoundedCornerShape(18.dp))
.padding(start = 13.dp, top = 8.dp),
visualTransformation = PasswordVisualTransformation()
)
if (isRegistration) {
Button(
onClick = {
var isExist = false;
if (password.isNotEmpty() && username.isNotEmpty()) {
users.value.forEach { user ->
if (user.login == username) {
Log.d("User already exist. User id: ", user.uid.toString())
isExist = true
}
}
if (!isExist) {
val newUser = User(null, username, password)
coroutineScope.launch {
val insertResult = async {
registerUserViewModel.insertUser(newUser)
}
insertResult.await()
registerUserViewModel.setUserList()
registerUserViewModel.users.observeForever { userList ->
users.value = userList
Log.println(Log.ASSERT, "UsersList", users.value.toString())
users.value?.forEach { user ->
if (user.password == password) {
currentUserViewModel.setArgument(user.uid.toString())
//navController.navigate(route = Graph.passUserId(user.id.toString()))
}
}
}
}
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text("Регистрация")
}
Text(
text = "Уже есть аккаунт? Войти",
modifier = Modifier
.clickable {
isRegistration = false
}
.align(Alignment.CenterHorizontally),
color = MaterialTheme.colorScheme.onBackground
)
} else {
Button(
onClick = {
Log.d("Кнопка","Тык")
if (password.isNotEmpty()) {
users_entry.value.forEach { user ->
if (user.password == password) {
currentUserViewModel.setArgument(user.uid.toString())
Log.d("Авторизация","Успешно")
//navController.navigate(route = Graph.passUserId(user.id.toString())) {
}else{
Log.d("Авторизация","Пароль не совпадает")
}
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text("Вход")
}
Text(
text = "Нет аккаунта? Зарегистрироваться",
modifier = Modifier
.clickable {
isRegistration = true
}
.align(Alignment.CenterHorizontally),
color = MaterialTheme.colorScheme.onBackground
)
}
val switchColors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.primary, // Change the color when the switch is checked
checkedTrackColor = MaterialTheme.colorScheme.secondary, // Change the color of the track when the switch is checked
uncheckedThumbColor = MaterialTheme.colorScheme.primary, // Change the color when the switch is unchecked
uncheckedTrackColor = MaterialTheme.colorScheme.onPrimary // Change the color of the track when the switch is unchecked
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.End
) {
Text(
"Темная тема", modifier = Modifier
.align(Alignment.CenterVertically)
.padding(5.dp)
)
val coroutine = rememberCoroutineScope()
Switch(
checked = isDarkTheme.value,
onCheckedChange = {
isDarkTheme.value = !isDarkTheme.value
coroutine.launch {
dataStoreManager.saveSettings(SettingData(isDarkTheme = isDarkTheme.value))
}
},
colors = switchColors
)
}
}
}
}
}
/*@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 UserProfilePreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
UserProfile(navController = null, isDarkTheme = remember { mutableStateOf(true) })
}
}
}*/

View File

@ -0,0 +1,119 @@
package com.example.myapplication.database.entities.composeui.edit
import android.content.res.Configuration
import android.graphics.BitmapFactory
import androidx.compose.foundation.layout.Column
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.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import com.example.myapplication.ui.theme.PmudemoTheme
import kotlinx.coroutines.launch
@Composable
fun BikeEdit(
navController: NavController,
viewModel: BikeEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val coroutineScope = rememberCoroutineScope()
BikeEdit(
bikeUiState = viewModel.bikeUiState,
onClick = {
coroutineScope.launch {
viewModel.saveBike()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
)
}
@Composable
private fun BikeEdit(
bikeUiState: BikeUiState,
onClick: () -> Unit,
onUpdate: (BikeDetails) -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = bikeUiState.bikeDetails.name,
onValueChange = { onUpdate(bikeUiState.bikeDetails.copy(name = it)) },
label = { Text(stringResource(id = R.string.Bike_name)) },
singleLine = true,
textStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSecondary
)
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = bikeUiState.bikeDetails.description,
onValueChange = { onUpdate(bikeUiState.bikeDetails.copy(description = it)) },
label = { Text(stringResource(id = R.string.Bike_description)) },
singleLine = true,
textStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSecondary
)
)
if (bikeUiState.bikeDetails.image != null)
ImageUploader(
bitmap = BitmapFactory.decodeByteArray(
bikeUiState.bikeDetails.image,
0,
bikeUiState.bikeDetails.image.size
),
onResult = { byteArray: ByteArray? ->
onUpdate(
bikeUiState.bikeDetails.copy(
image = byteArray
)
)
}
)
Button(
onClick = onClick,
enabled = bikeUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.Save_button))
}
}
}
@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 BikeEditPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
BikeEdit(
bikeUiState = BikeUiState(),
onClick = {},
onUpdate = {},
)
}
}
}

View File

@ -0,0 +1,92 @@
package com.example.myapplication.database.entities.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 com.example.myapplication.database.entities.model.Bike
import com.example.myapplication.database.entities.model.BikeWithItems
import com.example.myapplication.database.entities.model.ItemFromBike
import com.example.myapplication.database.entities.repository.BikeRepository
import kotlinx.coroutines.launch
class BikeEditViewModel(
savedStateHandle: SavedStateHandle,
private val bikeRepository: BikeRepository
) : ViewModel() {
var bikeUiState by mutableStateOf(BikeUiState())
private set
private val bikeUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (bikeUid > 0) {
bikeUiState = bikeRepository.getBike(bikeUid)
.toUiState(true)
}
}
}
fun updateUiState(bikeDetails: BikeDetails) {
bikeUiState = BikeUiState(
bikeDetails = bikeDetails,
isEntryValid = validateInput(bikeDetails)
)
}
suspend fun saveBike() {
if (validateInput()) {
if (bikeUid > 0) {
bikeRepository.updateBike(bikeUiState.bikeDetails.toBike(bikeUid))
} else {
bikeRepository.insertBike(bikeUiState.bikeDetails.toBike())
}
}
}
private fun validateInput(uiState: BikeDetails = bikeUiState.bikeDetails): Boolean {
return with(uiState) {
name.isNotBlank()
&& description.isNotBlank()
}
}
}
data class BikeUiState(
val bikeDetails: BikeDetails = BikeDetails(),
val isEntryValid: Boolean = false
)
data class BikeDetails(
val name: String = "",
val description: String = "",
val image: ByteArray? = byteArrayOf(),
val items: List<ItemFromBike> = emptyList()
)
fun BikeDetails.toBike(uid: Int = 0): Bike = Bike(
uid = uid,
name = name,
description = description,
image = image
)
fun BikeWithItems.toDetails(): BikeDetails {
val bike = this.bike
val items = this.items
return BikeDetails(
name = bike.name,
description = bike.description,
image = bike.image,
items = items
)
}
fun BikeWithItems.toUiState(isEntryValid: Boolean = false): BikeUiState = BikeUiState(
bikeDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,121 @@
package com.example.myapplication.database.entities.composeui.edit
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.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
import java.io.ByteArrayOutputStream
@Composable
fun ImageUploader(
bitmap: Bitmap?,
onResult: (ByteArray) -> 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)
val scaledBitmap = resizeBitmapWithAspectRatio(newBitmap, 200)
val stream = ByteArrayOutputStream()
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
onResult(stream.toByteArray())
}
}
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(10.dp)
)
.aspectRatio(1f)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(10.dp)
)
.clickable { launcher.launch("image/*") }
.padding(16.dp)
) {
if (bitmap != null) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(shape = RoundedCornerShape(10.dp))
)
} else {
Image(
painter = painterResource(id = R.drawable.photo),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(shape = RoundedCornerShape(10.dp))
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = title,
color = MaterialTheme.colorScheme.onBackground
)
}
}
fun resizeBitmapWithAspectRatio(bitmap: Bitmap, maxHeight: Int): Bitmap {
if (bitmap.height <= maxHeight) {
return bitmap
}
val aspectRatio = bitmap.width.toFloat() / bitmap.height
val newWidth = (maxHeight * aspectRatio).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, maxHeight, true)
}

View File

@ -0,0 +1,153 @@
package com.example.myapplication.database.entities.composeui.edit
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDefaults
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.database.entities.composeui.AppViewModelProvider
import kotlinx.coroutines.launch
import org.threeten.bp.Instant
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.ZoneId
import org.threeten.bp.ZoneOffset
@Composable
fun ItemEdit(
navController: NavController,
viewModel: ItemEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val coroutineScope = rememberCoroutineScope()
ItemEdit(
itemUiState = viewModel.itemUiState,
onClick = {
coroutineScope.launch {
viewModel.saveItem()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
fun Long.toLocalDate(): org.threeten.bp.LocalDate {
val instant = Instant.ofEpochMilli(this)
return instant.atZone(ZoneId.systemDefault()).toLocalDate()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ItemEdit(
itemUiState: ItemUiState,
onClick: () -> Unit,
onUpdate: (ItemDetails) -> Unit,
) {
LazyColumn(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
) {
item {
if (itemUiState.itemDetails.dateTime != LocalDateTime.MIN) {
val selectedDateMillis =
itemUiState.itemDetails.dateTime.toInstant(ZoneOffset.UTC).toEpochMilli()
val dateState = rememberDatePickerState(
initialDisplayMode = DisplayMode.Input,
initialSelectedDateMillis = selectedDateMillis
)
/* Surface(
tonalElevation = 6.dp,
color = MaterialTheme.colorScheme.primary
) {*/
DatePicker(
state = dateState,
modifier = Modifier.padding(16.dp),
colors = DatePickerDefaults.colors(
dayContentColor = MaterialTheme.colorScheme.primary,
)
)
// }
val selectedDate = dateState.selectedDateMillis?.toLocalDate()
if (selectedDate != null) {
val resultDateTime = LocalDateTime.of(selectedDate, LocalTime.MIDNIGHT)
onUpdate(itemUiState.itemDetails.copy(dateTime = resultDateTime))
}
} else {
val dateState = rememberDatePickerState(
initialDisplayMode = DisplayMode.Input
)
/* Surface(
tonalElevation = 6.dp,
color = MaterialTheme.colorScheme.primary
) {*/
DatePicker(
state = dateState,
modifier = Modifier.padding(16.dp),
colors = DatePickerDefaults.colors(
dayContentColor = MaterialTheme.colorScheme.primary,
)
)
// }
val selectedDate = dateState.selectedDateMillis?.toLocalDate()
if (selectedDate != null) {
val resultDateTime = LocalDateTime.of(selectedDate, LocalTime.MIDNIGHT)
onUpdate(itemUiState.itemDetails.copy(dateTime = resultDateTime))
}
}
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = itemUiState.itemDetails.weight,
label = { Text(text = "Вес") },
onValueChange = {
onUpdate(itemUiState.itemDetails.copy(weight = it))
},
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
textStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSecondary
)
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = itemUiState.itemDetails.maxCount.toString(),
onValueChange = { newValue ->
val parsedMaxCount = newValue.toIntOrNull() ?: 0 // Преобразование в Int
onUpdate(itemUiState.itemDetails.copy(maxCount = parsedMaxCount))
},
label = { Text(text = "Количество") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
textStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSecondary
)
)
Button(
onClick = onClick,
enabled = itemUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.Save_button))
}
}
}
}

View File

@ -0,0 +1,106 @@
package com.example.myapplication.database.entities.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 com.example.myapplication.database.entities.model.Item
import com.example.myapplication.database.entities.repository.ItemRepository
import kotlinx.coroutines.launch
import org.threeten.bp.LocalDateTime
class ItemEditViewModel(
savedStateHandle: SavedStateHandle,
private val itemRepository: ItemRepository
) : ViewModel() {
var itemUiState by mutableStateOf(ItemUiState())
private set
private val itemUid: Int = checkNotNull(savedStateHandle["id"])
private val bikeUid: Int = checkNotNull(savedStateHandle["bikeId"])
init {
viewModelScope.launch {
if (itemUid > 0) {
itemUiState = itemRepository.getItem(itemUid)
.toUiState(true)
}
}
}
fun updateUiState(itemDetails: ItemDetails) {
itemUiState = ItemUiState(
itemDetails = itemDetails,
isEntryValid = validateInput(itemDetails)
)
}
suspend fun saveItem() {
if (validateInput()) {
if (bikeUid > 0)
if (itemUid > 0) {
itemRepository.updateItem(
itemUiState.itemDetails
.toItem(uid = itemUid, bikeUid = bikeUid)
)
} else {
itemRepository.insertItem(
itemUiState.itemDetails.toItem(
bikeUid = bikeUid
)
)
}
}
}
private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean {
return with(uiState) {
dateTime != LocalDateTime.MIN
&& isValidDouble(weight)
&& maxCount > 0
&& bikeUid > 0
}
}
}
val regex = """^-?\d+(.\d+)?+(,\d+)?$""".toRegex()
fun isValidDouble(input: String): Boolean {
return regex.matches(input)
}
data class ItemUiState(
val itemDetails: ItemDetails = ItemDetails(),
val isEntryValid: Boolean = false
)
data class ItemDetails(
val uid: Int = 0,
val dateTime: LocalDateTime = LocalDateTime.MIN,
val weight: String = "0",
val maxCount: Int = 0,
val bikeId: Int = 0
)
fun ItemDetails.toItem(uid: Int = 0, bikeUid: Int = 0): Item = Item(
uid = uid,
dateTime = dateTime,
weight = weight.toDoubleOrNull() ?: 0.0,
maxCount = maxCount,
bikeId = bikeUid
)
fun Item.toDetails(): ItemDetails = ItemDetails(
uid = uid,
dateTime = dateTime,
weight = weight.toString(),
maxCount = maxCount,
bikeId = bikeId
)
fun Item.toUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState(
itemDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,39 @@
package com.example.myapplication.database.entities.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Bike
import com.example.myapplication.database.entities.model.ItemFromBike
import kotlinx.coroutines.flow.Flow
@Dao
interface BikeDao {
@Query("select * from bikes order by name")
fun getAll(): PagingSource<Int, Bike>
@Query(
"SELECT c.*, s.uid as item_uid, s.date_time, s.weight, s.max_count-IFNULL(SUM(os.count), 0) as available_count, c.uid as bike_id " +
"FROM bikes AS c " +
"LEFT JOIN items AS s ON s.bike_id = c.uid " +
"LEFT JOIN rents_items AS os ON os.item_id = s.uid " +
"WHERE c.uid = :bikeId " +
"GROUP BY item_uid"
)
fun getByUid(bikeId: Int?): Flow<Map<Bike, List<ItemFromBike>>>
@Insert
suspend fun insert(vararg bike: Bike)
@Update
suspend fun update(bike: Bike)
@Delete
suspend fun delete(bike: Bike)
@Query("DELETE FROM bikes")
suspend fun deleteAll()
}

View File

@ -0,0 +1,35 @@
package com.example.myapplication.database.entities.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Item
@Dao
interface ItemDao {
@Query("select * from items where items.uid = :uid")
suspend fun getByUid(uid: Int): Item
@Insert
suspend fun insert(vararg item: Item)
@Update
suspend fun update(item: Item)
@Delete
suspend fun delete(item: Item)
@Query("DELETE FROM items")
suspend fun deleteAll()
@Query(
"SELECT s.max_count-IFNULL(SUM(os.count), 0) as available_count " +
"FROM items AS s " +
"LEFT JOIN rents_items AS os ON os.item_id = s.uid " +
"WHERE s.uid = :itemId " +
"GROUP BY s.uid"
)
suspend fun getAvailableCountOfItem(itemId: Int): Int
}

View File

@ -0,0 +1,37 @@
package com.example.myapplication.database.entities.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.Rent
import com.example.myapplication.database.entities.model.ItemFromRent
@Dao
interface RentDao {
@Query("select * from rents where user_id = :userId")
fun getAll(userId: Int?): PagingSource<Int, Rent>
@Query(
"SELECT o.*, s.*, os.count, os.frozen_weight " +
"FROM rents AS o " +
"JOIN rents_items AS os ON os.rent_id = o.uid " +
"JOIN items AS s ON s.uid = os.item_id " +
"WHERE o.uid = :rentId"
)
fun getByUid(rentId: Int?): List<ItemFromRent>
@Insert
suspend fun insert(vararg rent: Rent): List<Long>
@Update
suspend fun update(rent: Rent)
@Delete
suspend fun delete(rent: Rent)
@Query("DELETE FROM rents")
suspend fun deleteAll()
}

View File

@ -0,0 +1,27 @@
package com.example.myapplication.database.entities.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.RentItemCrossRef
@Dao
interface RentItemCrossRefDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(rentItemCrossRef: RentItemCrossRef)
@Update
suspend fun update(rentItemCrossRef: RentItemCrossRef)
@Delete
suspend fun delete(rentItemCrossRef: RentItemCrossRef)
@Query("DELETE FROM rents_items where rents_items.rent_id = :rentId")
suspend fun deleteByRentUid(rentId: Int)
@Query("DELETE FROM rents_items where rents_items.item_id = :itemId")
suspend fun deleteByItemUid(itemId: Int)
}

View File

@ -0,0 +1,42 @@
package com.example.myapplication.database.entities.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.myapplication.database.entities.model.ItemFromCart
import com.example.myapplication.database.entities.model.User
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from users")
suspend fun getAll(): List<User>
@Query("select * from users where users.uid = :idUser")
fun getUserById(idUser: Int?): Flow<User>
@Query(
"SELECT items.*, items.max_count-IFNULL(SUM(rents_items.count), 0) as available_count, " +
"users_items.count FROM items " +
"join users_items on items.uid = users_items.item_id " +
"left join rents_items on items.uid = rents_items.item_id " +
"where users_items.user_id = :userId " +
"GROUP BY users_items.item_id "
)
suspend fun getCartByUid(userId: Int): List<ItemFromCart>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("DELETE FROM users")
suspend fun deleteAll()
}

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