Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

132 changed files with 2 additions and 9959 deletions

16
.gitignore vendored
View File

@ -1,16 +0,0 @@
*.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
/server/node_modules/

3
.idea/.gitignore vendored
View File

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

View File

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

View File

@ -1,20 +0,0 @@
<?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

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

View File

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

View File

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

View File

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

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# PIbd-32_Artamonova_T.V._Labs_PMU

1
app/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -1,101 +0,0 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "ru.ulstu.is.airticketrentservice"
compileSdk = 34
defaultConfig {
applicationId = "ru.ulstu.is.airticketrentservice"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.5"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
// Core
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-beta01")
// 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:1.1.2")
// 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")
implementation("com.google.android.material:material:1.4.0")
// Tests
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
debugImplementation("androidx.compose.material:material-icons-extended:1.5.3")
implementation("androidx.compose.material:material:1.4.3")
//Paging
implementation ("androidx.paging:paging-compose:3.2.1")
implementation ("androidx.paging:paging-runtime-ktx:3.2.1")
// 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")
}

View File

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

View File

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

View File

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

View File

@ -1,28 +0,0 @@
package ru.ulstu.`is`.airticketrentservice
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import ru.ulstu.`is`.airticketrentservice.graphs.RootNavigationGraph
//import ru.ulstu.`is`.airticketrentservice.navigation.MainNavbar
import ru.ulstu.`is`.airticketrentservice.ui.theme.AirTicketRentServiceTheme
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
class MainActivity : ComponentActivity() {
private val currentUserViewModel: CurrentUserViewModel by viewModels()
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AirTicketRentServiceTheme {
RootNavigationGraph(navController = rememberNavController(), currentUserViewModel = viewModel(factory = AppViewModelProvider.Factory))
}
}
}
}

View File

@ -1,15 +0,0 @@
package ru.ulstu.`is`.airticketrentservice
import android.app.Application
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.AppDataContainer
class TicketApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
}

View File

@ -1,157 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
import ru.ulstu.`is`.airticketrentservice.api.model.FlightRemote
import ru.ulstu.`is`.airticketrentservice.api.model.RentRemote
import ru.ulstu.`is`.airticketrentservice.api.model.TicketRemote
import ru.ulstu.`is`.airticketrentservice.api.model.UserRemote
interface AppService {
@GET("users")
suspend fun getAllUsers(): List<UserRemote>
@GET("rents")
suspend fun getAllRents(): List<RentRemote>
@GET("rents")
suspend fun getRents(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<RentRemote>
@GET("flights")
suspend fun findFlights(
@Query("direction_from") direction_from: String,
@Query("direction_to") direction_to: String,
@Query("departure_date") departure_date: String
): List<FlightRemote>
@GET("tickets")
suspend fun getAllTickets(): List<TicketRemote>
@GET("flights")
suspend fun getAllFlights(): List<FlightRemote>
@GET("tickets")
suspend fun getTickets(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<TicketRemote>
@GET("flights")
suspend fun getFlights(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<FlightRemote>
@GET("tickets")
suspend fun getFlightsTickets(@Query("flightId") flightId: Int): List<TicketRemote>
@GET("rents")
suspend fun getUserRents(@Query("userId") userId: Int): List<RentRemote>
@GET("users/{id}")
suspend fun getUser(
@Path("id") id: Int,
): 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("rents/{id}")
suspend fun getRent(
@Path("id") id: Int,
): RentRemote
@POST("rents")
suspend fun createRent(
@Body rent: RentRemote,
): RentRemote
@PUT("rents/{id}")
suspend fun updateRent(
@Path("id") id: Int,
@Body rent: RentRemote,
): RentRemote
@DELETE("rents/{id}")
suspend fun deleteRent(
@Path("id") id: Int,
): RentRemote
@GET("flights/{id}")
suspend fun getFlight(
@Path("id") id: Int,
): FlightRemote
@POST("flights")
suspend fun createFlight(
@Body flight: FlightRemote,
): FlightRemote
@PUT("flights/{id}")
suspend fun updateFlight(
@Path("id") id: Int,
@Body flight: FlightRemote,
): FlightRemote
@DELETE("flights/{id}")
suspend fun deleteFlight(
@Path("id") id: Int,
): FlightRemote
@GET("tickets/{id}")
suspend fun getTicket(
@Path("id") id: Int,
): TicketRemote
@POST("tickets")
suspend fun createTicket(
@Body ticket: TicketRemote,
): TicketRemote
@PUT("tickets/{id}")
suspend fun updateTicket(
@Path("id") id: Int,
@Body ticket: TicketRemote,
): TicketRemote
@DELETE("tickets/{id}")
suspend fun deleteTicket(
@Path("id") id: Int,
): TicketRemote
companion object {
private const val BASE_URL = "http://192.168.1.100:8079/"
@Volatile
private var INSTANCE: AppService? = null
fun getInstance(): AppService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(AppService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -1,107 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import retrofit2.HttpException
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.model.toFlight
import ru.ulstu.`is`.airticketrentservice.database.AppDatabase
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeyType
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeys
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineFlightRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class FlightRemoteMediator(
private val service: AppService,
private val dbFlightRepository: OfflineFlightRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Flight>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Flight>
): 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 flights = service.getFlights(page, state.config.pageSize).map { it.toFlight() }
val endOfPaginationReached = flights.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.FLIGHT)
dbFlightRepository.clearFlights()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = flights.map {
RemoteKeys(
entityId = it.id,
type = RemoteKeyType.FLIGHT,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbFlightRepository.insertFlights(flights)
}
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, Flight>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { flight ->
flight.id.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.FLIGHT) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Flight>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { flight ->
flight.id.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.FLIGHT) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Flight>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { flightid ->
dbRemoteKeyRepository.getAllRemoteKeys(flightid, RemoteKeyType.FLIGHT)
}
}
}
}

View File

@ -1,112 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import retrofit2.HttpException
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.model.toRent
import ru.ulstu.`is`.airticketrentservice.api.repository.RestTicketRepository
import ru.ulstu.`is`.airticketrentservice.api.repository.RestUserRepository
import ru.ulstu.`is`.airticketrentservice.database.AppDatabase
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRentRepository
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeyType
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeys
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class RentRemoteMediator(
private val service: AppService,
private val dbRentRepository: OfflineRentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val userRestRepository: RestUserRepository,
private val ticketRestRepository: RestTicketRepository,
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.RENT)
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.id,
type = RemoteKeyType.RENT,
prevKey = prevKey,
nextKey = nextKey
)
}
userRestRepository.getAllUsers()
ticketRestRepository.getAllTickets()
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.id, RemoteKeyType.RENT)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Rent>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { rent ->
dbRemoteKeyRepository.getAllRemoteKeys(rent.id, RemoteKeyType.RENT)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Rent>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { rentUid ->
dbRemoteKeyRepository.getAllRemoteKeys(rentUid, RemoteKeyType.RENT)
}
}
}
}

View File

@ -1,111 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import retrofit2.HttpException
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.model.toTicket
import ru.ulstu.`is`.airticketrentservice.api.repository.RestFlightRepository
import ru.ulstu.`is`.airticketrentservice.api.repository.RestUserRepository
import ru.ulstu.`is`.airticketrentservice.database.AppDatabase
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeyType
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeys
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineTicketRepository
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class TicketRemoteMediator(
private val service: AppService,
private val dbTicketRepository: OfflineTicketRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val flightRestRepository: RestFlightRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Ticket>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Ticket>
): 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 tickets = service.getTickets(page, state.config.pageSize).map { it.toTicket() }
val endOfPaginationReached = tickets.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.TICKET)
dbTicketRepository.clearTickets()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = tickets.map {
RemoteKeys(
entityId = it.id,
type = RemoteKeyType.TICKET,
prevKey = prevKey,
nextKey = nextKey
)
}
flightRestRepository.getAllFlights()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbTicketRepository.insertTickets(tickets)
}
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, Ticket>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { ticket ->
dbRemoteKeyRepository.getAllRemoteKeys(ticket.id, RemoteKeyType.TICKET)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Ticket>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { ticket ->
dbRemoteKeyRepository.getAllRemoteKeys(ticket.id, RemoteKeyType.TICKET)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Ticket>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { ticketUid ->
dbRemoteKeyRepository.getAllRemoteKeys(ticketUid, RemoteKeyType.TICKET)
}
}
}
}

View File

@ -1,34 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.model
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import java.io.File
@Serializable
data class FlightRemote(
val id: Int = 0,
var direction_from: String = "",
var direction_to: String = "",
var departure_date: String = "",
var arrival_date: String = "",
val one_ticket_cost: Double = 0.0
)
fun FlightRemote.toFlight(): Flight = Flight(
id,
direction_from,
direction_to,
departure_date,
arrival_date,
one_ticket_cost
)
fun Flight.toFlightRemote(): FlightRemote = FlightRemote(
id,
direction_from,
direction_to,
departure_date,
arrival_date,
one_ticket_cost
)

View File

@ -1,26 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
@Serializable
data class RentRemote(
val id: Int = 0,
var status: String = "",
val userId: Int = 0,
val ticketId: Int = 0
)
fun RentRemote.toRent(): Rent = Rent(
id,
status,
userId,
ticketId
)
fun Rent.toRentRemote(): RentRemote = RentRemote(
id,
status,
userId,
ticketId
)

View File

@ -1,26 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
@Serializable
data class TicketRemote(
val id: Int = 0,
var passengers_count: Int = 0,
val ticket_cost: Double = 0.0,
val flightId: Int = 0
)
fun TicketRemote.toTicket(): Ticket = Ticket(
id,
passengers_count,
ticket_cost,
flightId
)
fun Ticket.toTicketRemote(): TicketRemote = TicketRemote(
id,
passengers_count,
ticket_cost,
flightId
)

View File

@ -1,38 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.airticketrentservice.database.models.User
@Serializable
data class UserRemote(
val id: Int = 0,
var surname: String="",
var name: String="",
var patronymic: String? = "",
var date_of_birth: String = "",
var email: String = "",
var password: String = "",
val role: String = ""
)
fun UserRemote.toUser(): User = User(
id,
surname,
name,
patronymic,
date_of_birth,
email,
password,
role
)
fun User.toUserRemote(): UserRemote = UserRemote(
id,
surname,
name,
patronymic,
date_of_birth,
email,
password,
role
)

View File

@ -1,112 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.repository
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.forEach
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.suspendCancellableCoroutine
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import androidx.lifecycle.viewModelScope
import kotlinx.serialization.json.Json
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.model.toFlight
import ru.ulstu.`is`.airticketrentservice.api.model.toFlightRemote
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.AppDatabase
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineFlightRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.airticketrentservice.api.mediator.FlightRemoteMediator
import ru.ulstu.`is`.airticketrentservice.api.model.FlightRemote
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class RestFlightRepository(
private val service: AppService,
private val dbFlightRepository: OfflineFlightRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : FlightRepository {
override fun getFlights(): Flow<PagingData<Flight>> {
Log.d(RestFlightRepository::class.simpleName, "Get flights")
val pagingSourceFactory = { dbFlightRepository.getAllFlightsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = FlightRemoteMediator(
service,
dbFlightRepository,
dbRemoteKeyRepository,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getFlightById(flightId: Int): Flight =
service.getFlight(flightId).toFlight()
override suspend fun findFlights(
from: String,
to: String,
departureDate: String
): List<Flight> {
Log.d(RestFlightRepository::class.simpleName, "Find flights")
val existFlights = dbFlightRepository.findFlights(from, to, departureDate).associateBy { it.id }.toMutableMap()
service.findFlights(from, to, departureDate)
.map { it.toFlight() }
.forEach { flight ->
existFlights[flight.id] = flight
}
return existFlights.map { it.value }.sortedBy { it.id }
}
override suspend fun getAllFlights(): List<Flight> {
Log.d(RestFlightRepository::class.simpleName, "Get all flights")
val existFlights = dbFlightRepository.getAllFlights().associateBy { it.id }.toMutableMap()
service.getAllFlights()
.map { it.toFlight() }
.forEach { flight ->
val existFlight = existFlights[flight.id]
if (existFlight == null) {
dbFlightRepository.insertFlight(flight)
} else if (existFlight != flight) {
dbFlightRepository.updateFlight(flight)
}
existFlights[flight.id] = flight
}
return existFlights.map { it.value }.sortedBy { it.id }
}
override suspend fun insertFlight(flight: Flight) {
service.createFlight(flight.toFlightRemote()).toFlight()
}
override suspend fun updateFlight(flight: Flight) {
service.updateFlight(flight.id, flight.toFlightRemote()).toFlight()
}
override suspend fun deleteFlight(flight: Flight) {
service.deleteFlight(flight.id).toFlight()
}
}

View File

@ -1,83 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.repository
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.mediator.RentRemoteMediator
import ru.ulstu.`is`.airticketrentservice.api.model.toRent
import ru.ulstu.`is`.airticketrentservice.api.model.toRentRemote
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.AppDatabase
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRentRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.RentRepository
class RestRentRepository(
private val service: AppService,
private val dbRentRepository: OfflineRentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val userRestRepository: RestUserRepository,
private val ticketRestRepository: RestTicketRepository,
private val database: AppDatabase
) : RentRepository {
override fun getRents(): Flow<PagingData<Rent>> {
Log.d(RestRentRepository::class.simpleName, "Get rents")
val pagingSourceFactory = { dbRentRepository.getAllRentsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = RentRemoteMediator(
service,
dbRentRepository,
dbRemoteKeyRepository,
userRestRepository,
ticketRestRepository,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getRentById(rentId: Int): Rent =
rentId.let { service.getRent(it).toRent() }
override suspend fun insertRent(rent: Rent) {
service.createRent(rent.toRentRemote()).toRent()
}
override suspend fun updateRent(rent: Rent) {
rent.id.let { service.updateRent(it, rent.toRentRemote()).toRent() }
}
override suspend fun deleteRent(rent: Rent) {
rent.id.let { service.deleteRent(it).toRent() }
}
override suspend fun getAllRents(): List<Rent> {
val existRents = dbRentRepository.getAllRents().associateBy { it.id }.toMutableMap()
service.getAllRents()
.map { it.toRent() }
.forEach { rent ->
val existRent = existRents[rent.id]
if (existRent == null) {
dbRentRepository.insertRent(rent)
} else if (existRent != rent) {
dbRentRepository.updateRent(rent)
}
existRents[rent.id] = rent
}
return existRents.map { it.value }.sortedBy { it.id }
}
}

View File

@ -1,100 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.repository
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.mediator.TicketRemoteMediator
import ru.ulstu.`is`.airticketrentservice.api.model.toTicket
import ru.ulstu.`is`.airticketrentservice.api.model.toTicketRemote
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.AppDatabase
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineTicketRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.TicketRepository
class RestTicketRepository(
private val service: AppService,
private val dbTicketRepository: OfflineTicketRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val flightRestRepository: RestFlightRepository,
private val database: AppDatabase
) : TicketRepository {
override fun getTickets(): Flow<PagingData<Ticket>> {
Log.d(RestTicketRepository::class.simpleName, "Get tickets")
val pagingSourceFactory = { dbTicketRepository.getAllTicketsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = TicketRemoteMediator(
service,
dbTicketRepository,
dbRemoteKeyRepository,
flightRestRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getFlightsTickets(flightId: Int): List<Ticket> {
val flightsTickets = dbTicketRepository.getFlightsTickets(flightId).associateBy { it.id }.toMutableMap()
service.getFlightsTickets(flightId)
.map { it.toTicket() }
.forEach { ticket ->
val flightsTicket = flightsTickets[ticket.id]
if (flightsTicket == null) {
dbTicketRepository.insertTicket(ticket)
} else if (flightsTicket != ticket) {
dbTicketRepository.updateTicket(ticket)
}
flightsTickets[ticket.id] = ticket
}
return flightsTickets.map { it.value }.sortedBy { it.id }
}
override suspend fun getTicketById(ticketId: Int): Ticket =
ticketId.let { service.getTicket(it).toTicket() }
override suspend fun insertTicket(ticket: Ticket) : Long {
val createdTicket = service.createTicket(ticket.toTicketRemote()).toTicket()
return createdTicket.id.toLong()
}
override suspend fun updateTicket(ticket: Ticket) {
ticket.id.let { service.updateTicket(it, ticket.toTicketRemote()).toTicket() }
}
override suspend fun deleteTicket(ticket: Ticket) {
ticket.id.let { service.deleteTicket(it).toTicket() }
}
override suspend fun getAllTickets(): List<Ticket> {
val existTickets = dbTicketRepository.getAllTickets().associateBy { it.id }.toMutableMap()
service.getAllTickets()
.map { it.toTicket() }
.forEach { ticket ->
val existTicket = existTickets[ticket.id]
if (existTicket == null) {
dbTicketRepository.insertTicket(ticket)
} else if (existTicket != ticket) {
dbTicketRepository.updateTicket(ticket)
}
existTickets[ticket.id] = ticket
}
return existTickets.map { it.value }.sortedBy { it.id }
}
}

View File

@ -1,74 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.api.repository
import android.util.Log
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.model.toFlight
import ru.ulstu.`is`.airticketrentservice.api.model.toRent
import ru.ulstu.`is`.airticketrentservice.api.model.toUser
import ru.ulstu.`is`.airticketrentservice.api.model.toUserRemote
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRentRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineUserRepository
class RestUserRepository(
private val service: AppService,
private val dbUserRepository: OfflineUserRepository,
private val dbRentRepository: OfflineRentRepository
) : UserRepository {
override suspend fun getUserById(userId: Int): User =
service.getUser(userId).toUser()
override suspend fun insertUser(user: User) {
Log.d(RestUserRepository::class.simpleName, "Add user")
service.createUser(user.toUserRemote()).toUser()
}
override suspend fun updateUser(user: User) {
Log.d(RestUserRepository::class.simpleName, "Update user")
service.updateUser(user.id, user.toUserRemote()).toUser()
}
override suspend fun deleteUser(user: User) {
Log.d(RestUserRepository::class.simpleName, "Delete user")
service.deleteUser(user.id).toUser()
}
override suspend fun getAllUsers(): List<User> {
Log.d(RestUserRepository::class.simpleName, "Get users")
val existUsers = dbUserRepository.getAllUsers().associateBy { it.id }.toMutableMap()
service.getAllUsers()
.map { it.toUser() }
.forEach { user ->
val existUser = existUsers[user.id]
if (existUser == null) {
dbUserRepository.insertUser(user)
} else if (existUser != user) {
dbUserRepository.updateUser(user)
}
existUsers[user.id] = user
}
return existUsers.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserRents(userId: Int): List<Rent> {
return try {
val usersRents = service.getUserRents(userId)
if (usersRents.isNotEmpty()) {
usersRents.map { it.toRent() }
} else {
emptyList()
}
} catch (e: Exception) {
e.message?.let { Log.d(RestUserRepository::class.simpleName, it) }
emptyList()
}
}
}

View File

@ -1,76 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database
import android.content.Context
import ru.ulstu.`is`.airticketrentservice.api.AppService
import ru.ulstu.`is`.airticketrentservice.api.repository.RestFlightRepository
import ru.ulstu.`is`.airticketrentservice.api.repository.RestRentRepository
import ru.ulstu.`is`.airticketrentservice.api.repository.RestTicketRepository
import ru.ulstu.`is`.airticketrentservice.api.repository.RestUserRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineFlightRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineRentRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineTicketRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.OfflineUserRepository
interface AppContainer {
val userRestRepository: RestUserRepository
val ticketRestRepository: RestTicketRepository
val rentRestRepository: RestRentRepository
val flightRestRepository: RestFlightRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val flightRepository: OfflineFlightRepository by lazy {
OfflineFlightRepository(AppDatabase.getInstance(context).flightDao())
}
private val ticketRepository: OfflineTicketRepository by lazy {
OfflineTicketRepository(AppDatabase.getInstance(context).ticketDao())
}
private val rentRepository: OfflineRentRepository by lazy {
OfflineRentRepository(AppDatabase.getInstance(context).rentDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
AppService.getInstance(),
userRepository,
rentRepository
)
}
override val flightRestRepository: RestFlightRepository by lazy {
RestFlightRepository(
AppService.getInstance(),
flightRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val ticketRestRepository: RestTicketRepository by lazy {
RestTicketRepository(
AppService.getInstance(),
ticketRepository,
remoteKeyRepository,
flightRestRepository,
AppDatabase.getInstance(context)
)
}
override val rentRestRepository: RestRentRepository by lazy {
RestRentRepository(
AppService.getInstance(),
rentRepository,
remoteKeyRepository,
userRestRepository,
ticketRestRepository,
AppDatabase.getInstance(context)
)
}
}

View File

@ -1,102 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import ru.ulstu.`is`.airticketrentservice.database.dao.UserDao
import ru.ulstu.`is`.airticketrentservice.database.dao.RentDao
import ru.ulstu.`is`.airticketrentservice.database.dao.TicketDao
import ru.ulstu.`is`.airticketrentservice.database.dao.RemoteKeysDao
import ru.ulstu.`is`.airticketrentservice.database.dao.FlightDao
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeys
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.models.User
@Database(entities = [RemoteKeys::class, User::class, Flight::class, Ticket::class, Rent::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun rentDao(): RentDao
abstract fun ticketDao(): TicketDao
abstract fun flightDao(): FlightDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "ticketservicedatabase6"
@Volatile
private var INSTANCE: AppDatabase? = null
private suspend fun populateDatabase() {
INSTANCE?.let { database ->
val userDao = database.userDao()
val user1 = User(1, "Пупкин","Василий","Иванович","2003-10-17", "user1@mail.ru", "user1", "user")
val user2 = User(2, "Иванов","Иван","Петрович","2005-05-17", "user2@mail.ru", "user2", "user")
val user3 = User(3, "Сидорова","Екатерина","Денисовна","2004-10-17", "user3@mail.ru", "user3", "user")
userDao.insert(user1)
userDao.insert(user2)
userDao.insert(user3)
val rentDao = database.rentDao()
val rent1 = Rent(id=1, "Подтверждено",userId = 2, ticketId = 4)
val rent2 = Rent(id=2, "Подтверждено",userId = 1, ticketId = 2)
val rent3 = Rent(id=3, "Ожидает подтверждения",userId = 3, ticketId = 5)
val rent4 = Rent(id=4, "Подтверждено",userId = 2, ticketId = 4)
val rent5 = Rent(id=5, "Подтверждено",userId = 1, ticketId = 2)
val rent6 = Rent(id=6, "Ожидает подтверждения",userId = 3, ticketId = 5)
rentDao.insert(rent1)
rentDao.insert(rent2)
rentDao.insert(rent3)
rentDao.insert(rent4)
rentDao.insert(rent5)
rentDao.insert(rent6)
val flightDao = database.flightDao()
val flight1 = Flight(1, "Ульяновск", "Москва","17-12-2023 16:00", "17-12-2023 19:00",2539.4)
val flight2 = Flight(2, "Ульяновск", "Париж","18-12-2023 12:25", "19-12-2023 17:15", 8362.2)
val flight3 = Flight(3, "Ульяновск", "Сочи","24-12-2023 17:28", "24-12-2023 19:23", 1934.5)
val flight4 = Flight(4, "Ульяновск", "Нижний Новгород","24-12-2023 08:10", "24-12-2023 10:00", 1934.5)
val flight5 = Flight(5, "Ульяновск", "Самара","27-12-2023 13:00", "27-12-2023 14:00", 1934.5)
val flight6 = Flight(6, "Ульяновск", "Крым","24-11-2023 17:25", "24-11-2023 19:50", 1934.5)
flightDao.insert(flight1)
flightDao.insert(flight2)
flightDao.insert(flight3)
flightDao.insert(flight4)
flightDao.insert(flight5)
flightDao.insert(flight6)
val ticketDao = database.ticketDao()
val ticket1 = Ticket(1, 1,2990.2, flightId = 1)
val ticket2 = Ticket(2, 3,8243.9,flightId = 2)
val ticket3 = Ticket(3, 2, 5034.34, flightId = 3)
val ticket4 = Ticket(4, 5, 17283.2, flightId = 4)
val ticket5 = Ticket(5, 1, 2119.2, flightId = 1)
val ticket6 = Ticket(6, 4, 23898.3, flightId = 4)
ticketDao.insert(ticket1)
ticketDao.insert(ticket2)
ticketDao.insert(ticket3)
ticketDao.insert(ticket4)
ticketDao.insert(ticket5)
ticketDao.insert(ticket6)
}
}
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 }
}
}
}
}

View File

@ -1,37 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.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 kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
@Dao
interface FlightDao {
@Query("select * from flights")
suspend fun getAll(): List<Flight>
@Query("select * from flights where flights.id = :flightId")
fun getFlightById(flightId: Int): Flow<Flight>
@Query("select * from flights where (:from = ' ' or flights.direction_from like :from) and (:to = ' ' or flights.direction_to like :to) and (:departureDate = ' ' or flights.departure_date like :departureDate)")
suspend fun findFlights(from: String, to: String, departureDate: String): List<Flight>
@Query("SELECT * FROM flights ORDER BY id ASC")
fun getFlights(): PagingSource<Int, Flight>
@Insert
suspend fun insert(vararg flight: Flight)
@Update
suspend fun update(flight: Flight)
@Delete
suspend fun delete(flight: Flight)
@Query("DELETE FROM flights")
suspend fun deleteAll()
}

View File

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

View File

@ -1,34 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.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 kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
@Dao
interface RentDao {
@Query("select * from rents")
fun getAll(): List<Rent>
@Query("select * from rents where rents.id = :rentId")
fun getRentById(rentId: Int): Flow<Rent>
@Query("SELECT * FROM rents ORDER BY id ASC")
fun getRents(): PagingSource<Int, Rent>
@Insert
suspend fun insert(vararg rent: Rent)
@Update
suspend fun update(rent: Rent)
@Delete
suspend fun delete(rent: Rent)
@Query("DELETE FROM rents")
suspend fun deleteAll()
}

View File

@ -1,39 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.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 kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
@Dao
interface TicketDao {
@Query("select * from tickets")
fun getAll(): List<Ticket>
@Query("SELECT * FROM tickets ORDER BY id ASC")
fun getTickets(): PagingSource<Int, Ticket>
@Query("select * from tickets where tickets.id = :ticketId")
fun getTicketById(ticketId: Int): Flow<Ticket>
@Query("select * from tickets where tickets.flight_id = :flightId")
fun getFlightsTickets(flightId: Int): List<Ticket>
@Insert
suspend fun insert(vararg ticket: Ticket) : List<Long>
@Update
suspend fun update(ticket: Ticket)
@Delete
suspend fun delete(ticket: Ticket)
@Query("DELETE FROM tickets")
suspend fun deleteAll()
}

View File

@ -1,32 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.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 kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
@Dao
interface UserDao {
@Query("select * from users")
suspend fun getAll(): List<User>
@Query("select * from users where users.id = :userId")
fun getUserById(userId: Int?): Flow<User>
@Query("select * from rents WHERE rents.user_id = :userId")
suspend fun getUserRents(userId: Int): List<Rent>
@Insert
suspend fun insert(user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
}

View File

@ -1,27 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "flights")
data class Flight(
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
var direction_from: String,
var direction_to: String,
var departure_date: String,
var arrival_date: String,
val one_ticket_cost: Double
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Flight
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}

View File

@ -1,27 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
FLIGHT(Flight::class.simpleName ?: "Flight"),
TICKET(Ticket::class.simpleName ?: "Ticket"),
RENT(Rent::class.simpleName ?: "Rent");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -1,48 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "rents",
foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["user_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE),
ForeignKey(
entity = Ticket::class,
parentColumns = ["id"],
childColumns = ["ticket_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE)
],
indices = [
Index(value = ["ticket_id"], unique = true)
])
data class Rent(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
var status: String,
@ColumnInfo(name = "user_id")
val userId: Int,
@ColumnInfo(name = "ticket_id")
val ticketId: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Rent
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}

View File

@ -1,6 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.models
enum class RoleEnum {
Admin,
User
}

View File

@ -1,37 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "tickets",
foreignKeys = [
ForeignKey(
entity = Flight::class,
parentColumns = ["id"],
childColumns = ["flight_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE)]
)
data class Ticket(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
var passengers_count: Int,
val ticket_cost: Double,
@ColumnInfo(name = "flight_id")
val flightId: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Ticket
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}

View File

@ -1,34 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "users",
indices = [Index(value = ["email"], unique = true)
])
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
var surname: String,
@ColumnInfo(name = "user_name")
var name: String,
var patronymic: String?,
var date_of_birth: String,
var email: String,
var password: String,
val role: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as User
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}

View File

@ -1,15 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
interface FlightRepository {
suspend fun getAllFlights(): List<Flight>
suspend fun insertFlight(flight: Flight)
suspend fun updateFlight(flight: Flight)
suspend fun deleteFlight(flight: Flight)
suspend fun getFlightById(flightId: Int): Flight
suspend fun findFlights(from: String, to: String, departureDate: String): List<Flight>
fun getFlights(): Flow<PagingData<Flight>>
}

View File

@ -1,32 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import ru.ulstu.`is`.airticketrentservice.database.dao.FlightDao
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
class OfflineFlightRepository(private val flightDao: FlightDao) : FlightRepository{
override suspend fun getAllFlights(): List<Flight> = flightDao.getAll()
override suspend fun insertFlight(flight: Flight) = flightDao.insert(flight)
override suspend fun updateFlight(flight: Flight) = flightDao.update(flight)
override suspend fun deleteFlight(flight: Flight) = flightDao.delete(flight)
override suspend fun getFlightById(flightId: Int): Flight = flightDao.getFlightById(flightId).first()
override fun getFlights(): Flow<PagingData<Flight>> = Pager(
config = PagingConfig(
pageSize = 4,
enablePlaceholders = false
),
pagingSourceFactory = flightDao::getFlights
).flow
fun getAllFlightsPagingSource(): PagingSource<Int, Flight> = flightDao.getFlights()
override suspend fun findFlights(from: String, to: String, departureDate: String): List<Flight> = flightDao.findFlights(from, to, departureDate)
suspend fun clearFlights() = flightDao.deleteAll()
suspend fun insertFlights(flights: List<Flight>) =
flightDao.insert(*flights.toTypedArray())
}

View File

@ -1,16 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import ru.ulstu.`is`.airticketrentservice.database.dao.RemoteKeysDao
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeyType
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeys
class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeysRepository {
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
remoteKeysDao.getRemoteKeys(id, type)
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys?>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -1,30 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import ru.ulstu.`is`.airticketrentservice.database.dao.RentDao
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
class OfflineRentRepository(private val rentDao: RentDao) : RentRepository{
override suspend fun insertRent(rent: Rent) = rentDao.insert(rent)
override suspend fun updateRent(rent: Rent) = rentDao.update(rent)
override suspend fun deleteRent(rent: Rent) = rentDao.delete(rent)
override suspend fun getAllRents(): List<Rent> = rentDao.getAll()
override suspend fun getRentById(rentId: Int): Rent = rentDao.getRentById(rentId).first()
override fun getRents(): Flow<PagingData<Rent>> = Pager(
config = PagingConfig(
pageSize = 4,
enablePlaceholders = false
),
pagingSourceFactory = rentDao::getRents
).flow
fun getAllRentsPagingSource(): PagingSource<Int, Rent> = rentDao.getRents()
suspend fun clearRents() = rentDao.deleteAll()
suspend fun insertRents(rents: List<Rent>) =
rentDao.insert(*rents.toTypedArray())
}

View File

@ -1,30 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import ru.ulstu.`is`.airticketrentservice.database.dao.TicketDao
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
class OfflineTicketRepository(private val ticketDao: TicketDao) : TicketRepository {
override suspend fun insertTicket(ticket: Ticket) : Long = ticketDao.insert(ticket)[0]
override suspend fun updateTicket(ticket: Ticket) = ticketDao.update(ticket)
override suspend fun deleteTicket(ticket: Ticket) = ticketDao.delete(ticket)
override suspend fun getAllTickets(): List<Ticket> = ticketDao.getAll()
override suspend fun getTicketById(ticketId: Int): Ticket = ticketDao.getTicketById(ticketId).first()
override fun getTickets(): Flow<PagingData<Ticket>> = Pager(
config = PagingConfig(
pageSize = 4,
enablePlaceholders = false
),
pagingSourceFactory = ticketDao::getTickets
).flow
fun getAllTicketsPagingSource(): PagingSource<Int, Ticket> = ticketDao.getTickets()
override suspend fun getFlightsTickets(flightId: Int): List<Ticket> = ticketDao.getFlightsTickets(flightId)
suspend fun clearTickets() = ticketDao.deleteAll()
suspend fun insertTickets(tickets: List<Ticket>) =
ticketDao.insert(*tickets.toTypedArray())
}

View File

@ -1,15 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import kotlinx.coroutines.flow.first
import ru.ulstu.`is`.airticketrentservice.database.dao.UserDao
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.models.User
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override suspend fun insertUser(user: User) = userDao.insert(user)
override suspend fun updateUser(user: User) = userDao.update(user)
override suspend fun deleteUser(user: User) = userDao.delete(user)
override suspend fun getAllUsers(): List<User> = userDao.getAll()
override suspend fun getUserById(userId: Int): User = userDao.getUserById(userId).first()
override suspend fun getUserRents(id: Int): List<Rent> = userDao.getUserRents(id)
}

View File

@ -1,10 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeyType
import ru.ulstu.`is`.airticketrentservice.database.models.RemoteKeys
interface RemoteKeysRepository {
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys?>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -1,15 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
interface RentRepository {
suspend fun insertRent(rent: Rent)
suspend fun updateRent(rent: Rent)
suspend fun deleteRent(rent: Rent)
suspend fun getAllRents(): List<Rent>
suspend fun getRentById(rentId: Int): Rent
fun getRents(): Flow<PagingData<Rent>>
}

View File

@ -1,15 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
interface TicketRepository {
suspend fun insertTicket(ticket: Ticket) : Long
suspend fun updateTicket(ticket: Ticket)
suspend fun deleteTicket(ticket: Ticket)
suspend fun getAllTickets(): List<Ticket>
suspend fun getTicketById(ticketId: Int): Ticket
fun getTickets(): Flow<PagingData<Ticket>>
suspend fun getFlightsTickets(flightId: Int): List<Ticket>
}

View File

@ -1,13 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.database.repository
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.models.User
interface UserRepository {
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
suspend fun getAllUsers(): List<User>
suspend fun getUserById(userId: Int): User
suspend fun getUserRents(userId: Int): List<Rent>
}

View File

@ -1,59 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.elements
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EmailTextField(email: String, onEmailChange: (String) -> Unit) {
TextField(
value = email,
onValueChange = {
onEmailChange(it)
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier.fillMaxWidth(),
label = { Text("Электронная почта") },
placeholder = { Text("example@mail.ru") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
singleLine = true,
)
}
fun isValidEmail(email: String): Boolean {
val emailRegex = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
return email.matches(emailRegex)
}
@Composable
fun ValidateEmail(email: String, onEmailChange: (String) -> Unit) {
Column (modifier = Modifier.padding(16.dp)) {
EmailTextField(email = email, onEmailChange = { onEmailChange(it)})
if (email.isNotEmpty()) {
if (isValidEmail(email)) {
Text(text = "Корректно", color = Color(0xFF5D925E))
} else {
Text(text = "Не корректно", color = Color(0xFF94474D))
}
}
}
}

View File

@ -1,32 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.graphs
import android.window.SplashScreen
import androidx.compose.ui.Modifier
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import ru.ulstu.`is`.airticketrentservice.screen.auth.Login
import ru.ulstu.`is`.airticketrentservice.screen.auth.Registration
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.LoginViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RegistrationViewModel
fun NavGraphBuilder.authNavGraph(navController: NavHostController, registrationViewModel: RegistrationViewModel, loginViewModel: LoginViewModel, currentUserViewModel: CurrentUserViewModel){
navigation(
route=Graph.AUTHENTICATION,
startDestination = AuthScreen.Login.route
){
composable(route=AuthScreen.Login.route){
Login(navController = navController, Modifier, loginViewModel, currentUserViewModel)
}
composable(route=AuthScreen.Registration.route){
Registration(navController = navController, Modifier, registrationViewModel, currentUserViewModel)
}
}
}
sealed class AuthScreen(val route: String){
object Login: AuthScreen("login")
object Registration: AuthScreen("registration")
}

View File

@ -1,173 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.graphs
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.compose.NavHost
import androidx.navigation.navArgument
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.screen.Admin
import ru.ulstu.`is`.airticketrentservice.screen.FlightEdit
import ru.ulstu.`is`.airticketrentservice.screen.FlightInfo
import ru.ulstu.`is`.airticketrentservice.screen.FlightList
import ru.ulstu.`is`.airticketrentservice.screen.FoundFlights
import ru.ulstu.`is`.airticketrentservice.screen.MainPage
import ru.ulstu.`is`.airticketrentservice.screen.MyRents
import ru.ulstu.`is`.airticketrentservice.screen.Profile
import ru.ulstu.`is`.airticketrentservice.screen.RentEdit
import ru.ulstu.`is`.airticketrentservice.screen.RentList
import ru.ulstu.`is`.airticketrentservice.screen.RentStatusEdit
import ru.ulstu.`is`.airticketrentservice.screen.TicketEdit
import ru.ulstu.`is`.airticketrentservice.screen.TicketList
import ru.ulstu.`is`.airticketrentservice.screen.TicketView
import ru.ulstu.`is`.airticketrentservice.screen.UserEdit
import ru.ulstu.`is`.airticketrentservice.screen.UserList
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FindFlightsViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel
//import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel
@Composable
fun HomeNavGraph(
navController: NavHostController,
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory),
userListViewModel: UserListViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketListViewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory),
rentListViewModel: RentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
NavHost(
navController = navController,
route = Graph.MAIN,
startDestination = BottomBarScreen.MainPage.route
){
composable(
route = BottomBarScreen.MainPage.route
){
MainPage(navController, flightListViewModel)
}
composable(
route = BottomBarScreen.FlightList.route
) {
FlightList(navController)
}
composable(
route = BottomBarScreen.Profile.route
){
Profile(navController, currentUserViewModel)
}
composable(
route = BottomBarScreen.UserList.route
){
UserList(navController, userListViewModel)
}
composable(
route = BottomBarScreen.TicketList.route
){
TicketList(navController, flightListViewModel, ticketListViewModel)
}
composable(
route = BottomBarScreen.RentList.route
){
RentList(navController, userListViewModel, currentUserViewModel, ticketListViewModel, rentListViewModel)
}
composable(
route = BottomBarScreen.Admin.route
){
Admin(navController)
}
composable(
route = BottomBarScreen.FoundFlights.route,
arguments = listOf(
navArgument("direction_from") { type = NavType.StringType },
navArgument("direction_to") { type = NavType.StringType },
navArgument("departure_date") { type = NavType.StringType }
)
){
FoundFlights(navController)
}
composable(
route = BottomBarScreen.UserEdit.route,
arguments = listOf(
navArgument("id") {
type = NavType.IntType
}
)
){
UserEdit(navController)
}
composable(
route = BottomBarScreen.RentEdit.route,
arguments = listOf(
navArgument("id") {
type = NavType.IntType
}
)
){
RentEdit()
}
composable(
route = BottomBarScreen.RentStatusEdit.route,
arguments = listOf(
navArgument("id") {
type = NavType.IntType
}
)
){
RentStatusEdit(navController)
}
composable(
route = BottomBarScreen.FlightEdit.route,
arguments = listOf(
navArgument("id") {
type = NavType.IntType
}
)
){
FlightEdit(navController)
}
composable(
route = BottomBarScreen.FlightInfo.route,
arguments = listOf(
navArgument("id") {
type = NavType.IntType
}
)
){
FlightInfo(navController)
}
composable(
route = BottomBarScreen.MyRents.route,
arguments = listOf(
navArgument("userId") {
type = NavType.IntType
}
)
) {
MyRents(navController)
}
composable(
route = BottomBarScreen.TicketEdit.route,
arguments = listOf(
navArgument("id") { type = NavType.IntType }
)
) {
TicketEdit(navController)
}
composable(
route = BottomBarScreen.TicketView.route,
arguments = listOf(
navArgument("id") { type = NavType.IntType },
navArgument("flightId") { type = NavType.IntType }
)
) {
TicketView(navController, currentUserViewModel)
}
}
}

View File

@ -1,62 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.graphs
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.composable
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.navArgument
import ru.ulstu.`is`.airticketrentservice.screen.LoadScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.LoginViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RegistrationViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel
//import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel
const val USERID_ARGUMENT="userId"
@Composable
fun RootNavigationGraph(
navController: NavHostController,
userListViewModel: UserListViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
registrationViewModel: RegistrationViewModel= viewModel(factory = AppViewModelProvider.Factory),
loginViewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketListViewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory),
rentListViewModel: RentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
NavHost(
navController=navController,
route = Graph.ROOT,
startDestination = Graph.AUTHENTICATION
){
authNavGraph(navController = navController, registrationViewModel, loginViewModel, currentUserViewModel)
composable(route=Graph.MAIN,
arguments = listOf(navArgument(USERID_ARGUMENT){
type= NavType.StringType
})){
LoadScreen(
currentUserViewModel = currentUserViewModel,
flightListViewModel = flightListViewModel,
userListViewModel = userListViewModel,
ticketListViewModel = ticketListViewModel,
rentListViewModel = rentListViewModel
)
}
}
}
object Graph{
const val ROOT="root_graph"
const val AUTHENTICATION="auth_graph"
const val MAIN="main_graph/{$USERID_ARGUMENT}"
fun passUserId(userId: String): String{
return "main_graph/$userId"
}
}

View File

@ -1,113 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.navigation
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.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
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 androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
@Composable
fun BottomBar(navController: NavHostController, currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val screens = listOf(
BottomBarScreen.MainPage,
BottomBarScreen.Profile,
BottomBarScreen.Admin
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination=navBackStackEntry?.destination
val bottomBarDestination=screens.any{ it.route==currentDestination?.route }
Row(
modifier = Modifier
.background(Color.Transparent),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
if (bottomBarDestination) {
NavigationBar(
containerColor = colorResource(R.color.lightlightBlue),
modifier = Modifier
.padding(10.dp)
.clip(shape = RoundedCornerShape(20.dp))
) {
screens.forEach { screen ->
currentUserViewModel.user?.role?.let {
AddItem(
screen = screen,
currentDestination = currentDestination,
navController = navController,
userRole = it
)
}
}
}
}
}
}
@Composable
fun RowScope.AddItem(
screen: BottomBarScreen,
currentDestination: NavDestination?,
navController: NavController,
userRole: String
){
val isAdmin = userRole == "Admin"
val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
val background = if (selected) Color(0xFFFFFFFF) else Color.Transparent
if (isAdmin || screen != BottomBarScreen.Admin) {
Box(
modifier = Modifier
.height(40.dp)
.clip(CircleShape)
.background(background)
.clickable(onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id)
launchSingleTop = true
}
})
) {
Row(
modifier = Modifier.padding(start = 47.dp, end = 46.dp, top = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = screen.icon,
contentDescription = "icon",
tint = Color.DarkGray
)
}
}
} else {
Text(text = "Недостаточно прав", fontSize = 8.sp, modifier = Modifier.fillMaxWidth().padding(top = 10.dp))
}
}

View File

@ -1,133 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.navigation
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.AdminPanelSettings
import androidx.compose.material.icons.filled.AirplanemodeActive
import androidx.compose.material.icons.filled.FlightTakeoff
import androidx.compose.material.icons.filled.Payments
import androidx.compose.ui.graphics.vector.ImageVector
sealed class BottomBarScreen(
val route: String,
val title: String,
val icon: ImageVector,
){
object MainPage: BottomBarScreen(
route = "main",
title = "Поиск",
icon = Icons.Filled.AirplanemodeActive
)
object Profile: BottomBarScreen(
route = "profile",
title = "Профиль",
icon = Icons.Filled.AccountCircle
)
object Admin: BottomBarScreen(
route = "admin",
title ="Админ",
icon = Icons.Filled.AdminPanelSettings
)
object FlightList: BottomBarScreen(
route = "flight-list",
title ="",
icon = Icons.Filled.FlightTakeoff
)
object UserList: BottomBarScreen(
route = "user-list",
title ="",
icon = Icons.Filled.AccountCircle
)
object TicketList: BottomBarScreen(
route = "ticket-list",
title ="",
icon = Icons.Filled.AccountCircle
)
object RentList: BottomBarScreen(
route = "rent-list",
title ="",
icon = Icons.Filled.Payments
)
object MyRents: BottomBarScreen(
route = "my-rents/{userId}",
title = "Мои бронирования",
icon = Icons.Filled.Payments
) {
fun passId(userId: String): String{
return "my-rents/$userId"
}
}
object FoundFlights: BottomBarScreen(
route = "found-flights/{direction_from}/{direction_to}/{departure_date}",
title ="",
icon = Icons.Filled.FlightTakeoff
) {
fun passText(direction_from: String?, direction_to: String?, departure_date: String?): String {
return "found-flights/${direction_from?:" "}/${direction_to?:" "}/${departure_date?:" "}"
}
}
object UserEdit: BottomBarScreen(
route = "user-edit/{id}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id: String): String{
return "user-edit/$id"
}
}
object FlightEdit: BottomBarScreen(
route = "flight-edit/{id}",
title ="",
icon = Icons.Filled.FlightTakeoff
) {
fun passId(id: String): String{
return "flight-edit/$id"
}
}
object TicketView: BottomBarScreen(
route = "ticket-view/{id}/{flightId}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passIdAndFlightId(id: String, flightId: String): String{
return "ticket-view/$id/$flightId"
}
}
object FlightInfo: BottomBarScreen(
route = "flight-info/{id}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id: String): String{
return "flight-info/$id"
}
}
object RentEdit: BottomBarScreen(
route = "rent-edit/{id}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id: String): String{
return "rent-edit/$id"
}
}
object RentStatusEdit: BottomBarScreen(
route = "rent-status-edit/{id}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id: String): String{
return "rent-status-edit/$id"
}
}
object TicketEdit: BottomBarScreen(
route = "ticket-edit/{id}",
title ="",
icon = Icons.Filled.AccountCircle
) {
fun passId(id: String): String{
return "ticket-edit/$id"
}
}
}

View File

@ -1,100 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
@Composable
fun Admin (
navController: NavController,
){
Column (
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = { navController.navigate(BottomBarScreen.FlightList.route)},
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
) {
Text("Рейсы")
}
Button(
onClick = { navController.navigate(BottomBarScreen.UserList.route)},
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
) {
Text("Пользователи")
}
Button(
onClick = { navController.navigate(BottomBarScreen.RentList.route)},
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
) {
Text("Бронирования")
}
Button(
onClick = { navController.navigate(BottomBarScreen.TicketList.route)},
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
) {
Text("Билеты")
}
}
}

View File

@ -1,237 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.app.DatePickerDialog
import android.widget.DatePicker
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
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.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.R
import java.util.Calendar
import java.util.Date
@Composable
fun FlightEdit(
navController: NavController,
viewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
FlightEdit(
flightUiState = viewModel.flightUiState,
onClick = {
coroutineScope.launch {
viewModel.saveFlight()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FlightEdit(
flightUiState: FlightUiState,
onClick: () -> Unit,
onUpdate: (FlightDetails) -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.direction_from,
onValueChange = { onUpdate(flightUiState.flightDetails.copy(direction_from = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_from)) },
singleLine = true
)
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.direction_to,
onValueChange = { onUpdate(flightUiState.flightDetails.copy(direction_to = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_to)) },
singleLine = true
)
val mContext = LocalContext.current
val yearFrom: Int
val monthFrom: Int
val dayFrom: Int
// Initializing a Calendar
val calendarFrom = Calendar.getInstance()
// Fetching current year, month and day
yearFrom = calendarFrom.get(Calendar.YEAR)
monthFrom = calendarFrom.get(Calendar.MONTH)
dayFrom = calendarFrom.get(Calendar.DAY_OF_MONTH)
calendarFrom.time = Date()
// Declaring a string value to
// store date in string format
val dateFrom = remember { mutableStateOf("") }
// Declaring DatePickerDialog and setting
// initial values as current values (present year, month and day)
val datePickerDialogFrom = DatePickerDialog(
mContext,
{ _: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
val selectedDateFrom = "$dayOfMonth-${month + 1}-$year"
dateFrom.value = selectedDateFrom
onUpdate(flightUiState.flightDetails.copy(departure_date = selectedDateFrom))
}, yearFrom, monthFrom, dayFrom
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
onClick = {
datePickerDialogFrom.show()
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth(),
) {
Text("Выбрать дату вылета")
}
Text(text = " ${flightUiState.flightDetails.departure_date}", fontSize = 15.sp, color = Color.DarkGray)
// Declaring integer values
// for year, month and day
val yearTo: Int
val monthTo: Int
val dayTo: Int
// Initializing a Calendar
val calendarTo = Calendar.getInstance()
// Fetching current year, month and day
yearTo = calendarTo.get(Calendar.YEAR)
monthTo = calendarTo.get(Calendar.MONTH)
dayTo = calendarTo.get(Calendar.DAY_OF_MONTH)
calendarTo.time = Date()
// Declaring a string value to
// store date in string format
val dateTo = remember { mutableStateOf("") }
// Declaring DatePickerDialog and setting
// initial values as current values (present year, month and day)
val datePickerDialogTo = DatePickerDialog(
mContext,
{ _: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
val selectedDateTo = "$dayOfMonth-${month + 1}-$year"
dateTo.value = selectedDateTo
onUpdate(flightUiState.flightDetails.copy(arrival_date = selectedDateTo))
}, yearTo, monthTo, dayTo
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
onClick = {
datePickerDialogTo.show()
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth(),
) {
Text("Выбрать дату прилёта")
}
Text(text = flightUiState.flightDetails.arrival_date, fontSize = 15.sp, color = Color.DarkGray)
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.one_ticket_cost.toString(),
onValueChange = { onUpdate(flightUiState.flightDetails.copy(one_ticket_cost = it.toDouble())) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Стоимость одного билета") },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal)
)
Button(
onClick = onClick,
enabled = flightUiState.isEntryValid,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.ticket_save_button))
}
}
}

View File

@ -1,156 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.toFlight
@Composable
fun FlightInfo(
navController: NavController,
flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
FlightInfo(
flightUiState = flightViewModel.flightUiState,
onClick = {
Log.d("FlightInfo", "Текущий рейс для передачи в билет: ${flightViewModel.flightId}")
val route = BottomBarScreen.TicketView.passIdAndFlightId(0.toString(), flightViewModel.flightId.toString())
navController.navigate(route)
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FlightInfo(
flightUiState: FlightUiState,
onClick: () -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.direction_from,
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_from)) },
singleLine = true,
readOnly = true
)
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.direction_to,
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_to)) },
singleLine = true,
readOnly = true
)
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.departure_date,
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_arrivalDate)) },
singleLine = true,
readOnly = true
)
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.arrival_date,
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_departureDate)) },
singleLine = true,
readOnly = true
)
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = flightUiState.flightDetails.one_ticket_cost.toString(),
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Стоимость одного билета") },
singleLine = true,
readOnly = true
)
Button(
onClick = onClick,
enabled = flightUiState.isEntryValid,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Сформировать билет")
}
}
}

View File

@ -1,244 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeOut
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.DismissValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
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.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightListViewModel
@Composable
fun FlightList(
navController: NavController,
viewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val flightListUiState = viewModel.flightsListUiState.collectAsLazyPagingItems()
Scaffold(
topBar = {},
floatingActionButton = {
FloatingActionButton(
onClick = {
val route = BottomBarScreen.FlightEdit.passId(0.toString())
navController.navigate(route)
},
containerColor = colorResource(R.color.lightlightBlue)
) {
Icon(Icons.Filled.Add, "Добавить")
}
}
) { innerPadding ->
FlightList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
flightList = flightListUiState,
onClick = { id: Int ->
val route = BottomBarScreen.FlightEdit.passId(id.toString())
navController.navigate(route)
},
onSwipe = { flight: Flight ->
coroutineScope.launch {
viewModel.deleteFlight(flight)
}
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DismissBackground(dismissState: DismissState) {
val color = when (dismissState.dismissDirection) {
DismissDirection.StartToEnd -> Color.Transparent
DismissDirection.EndToStart -> Color(0xFFFF1744)
null -> Color.Transparent
}
val direction = dismissState.dismissDirection
Row(
modifier = Modifier
.fillMaxSize()
.background(color)
.padding(12.dp, 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
if (direction == DismissDirection.EndToStart) {
Icon(
Icons.Default.Delete,
contentDescription = "delete",
tint = Color.White
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
flight: Flight,
onClick: (id: Int) -> Unit
) {
SwipeToDismiss(
modifier = Modifier.zIndex(1f),
state = dismissState,
directions = setOf(
DismissDirection.EndToStart
),
background = {
DismissBackground(dismissState)
},
dismissContent = {
FlightListItem(flight = flight,
modifier = Modifier
.padding(vertical = 7.dp)
.clickable { onClick(flight.id) })
}
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun FlightList(
modifier: Modifier = Modifier,
flightList: LazyPagingItems<Flight>,
onClick: (id: Int) -> Unit,
onSwipe: (flight: Flight) -> Unit
) {
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
flightList.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
Box(
modifier = modifier.pullRefresh(state)
) {
Column(
modifier = modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(
count = flightList.itemCount,
key = flightList.itemKey(),
contentType = flightList.itemContentType()
) { index ->
val flight = flightList[index]
flight?.let {
var show by remember { mutableStateOf(true) }
val dismissState = rememberDismissState(
confirmValueChange = {
if (it == DismissValue.DismissedToStart ||
it == DismissValue.DismissedToEnd
) {
show = false
true
} else false
}, positionalThreshold = { 200.dp.toPx() }
)
AnimatedVisibility(
show, exit = fadeOut(spring())
) {
SwipeToDelete(
dismissState = dismissState,
flight = flight,
onClick = onClick
)
}
LaunchedEffect(show) {
if (!show) {
delay(800)
onSwipe(flight)
}
}
}
}
}
PullRefreshIndicator(
refreshing, state,
Modifier
.align(CenterHorizontally)
.zIndex(100f)
)
}
}
}
@Composable
private fun FlightListItem(
flight: Flight, modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = "${flight.direction_from} --> ${flight.direction_to}"
)
}
}
}

View File

@ -1,94 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
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.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FindFlightsViewModel
@Composable
fun FoundFlights(
navController: NavController,
viewModel: FindFlightsViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val foundFlightsUiState = viewModel.foundFlightsUiState
Scaffold(
topBar = {}
) { innerPadding ->
FoundFlights(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
flightList = foundFlightsUiState.flightList,
onClick = { uid: Int ->
Log.d("FoundFlights", "Текущий рейс: $uid")
val route = BottomBarScreen.FlightInfo.passId(uid.toString())
navController.navigate(route)
}
)
}
}
@Composable
private fun FoundFlights(
modifier: Modifier = Modifier,
flightList: List<Flight>,
onClick: (uid: Int) -> Unit
) {
Column(
modifier = modifier.fillMaxSize()
) {
if (flightList.isEmpty()) {
Text(
text = "Подходящие рейсы не найдены",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
} else {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(items = flightList, key = { it.id }) { flight ->
FlightListItem(flight = flight, modifier = Modifier.clickable{ onClick(flight.id) })
}
}
}
}
}
@Composable
private fun FlightListItem(
flight: Flight, modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth().padding(10.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(containerColor = colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(text = "${flight.direction_from} --> ${flight.direction_to}")
Text(text = "Цена: ${flight.one_ticket_cost}")
}
}
}

View File

@ -1,245 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.widget.DatePicker
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.stringResource
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 androidx.navigation.NavDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.graphs.HomeNavGraph
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBar
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.LoginViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RegistrationViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel
import java.util.Calendar
import java.util.Date
@Composable
fun MainPage(
navController: NavController,
flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
Scaffold(
) { innerPadding ->
MainPage(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
navController = navController
)
}
}
@Composable
fun MainPage(
modifier: Modifier = Modifier,
navController: NavController
) {
val from = remember { mutableStateOf<String?>(null) }
val to = remember { mutableStateOf<String?>(null) }
val departureDate = remember { mutableStateOf<String?>(null) }
val state = rememberScrollState()
LaunchedEffect(Unit) { state.animateScrollTo(100) }
Column(
Modifier
.padding(all = 10.dp)
.fillMaxSize()
.navigationBarsPadding()
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp)
.verticalScroll(state)
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
bitmap = ImageBitmap.imageResource(R.drawable.logo),
contentDescription = "Логотип",
modifier = Modifier.size(100.dp, 100.dp)
)
Text(
text = stringResource(id = R.string.main_heading),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
)
Divider(
color = Color.Black, thickness = 2.dp, modifier = Modifier
.padding(bottom = 24.dp)
.fillMaxWidth()
)
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = from.value?:"", onValueChange = { from.value = it },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.ticket_from))
}
)
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = to.value?:"", onValueChange = { to.value = it },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.ticket_to))
}
)
val mContext = LocalContext.current
val yearFrom: Int
val monthFrom: Int
val dayFrom: Int
val calendarFrom = Calendar.getInstance()
yearFrom = calendarFrom.get(Calendar.YEAR)
monthFrom = calendarFrom.get(Calendar.MONTH)
dayFrom = calendarFrom.get(Calendar.DAY_OF_MONTH)
calendarFrom.time = Date()
val dateFrom = remember { mutableStateOf("") }
val datePickerDialogFrom = DatePickerDialog(
mContext,
{ _: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
val selectedDateFrom = "$dayOfMonth-${month + 1}-$year"
dateFrom.value = selectedDateFrom
departureDate.value = selectedDateFrom
}, yearFrom, monthFrom, dayFrom
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
onClick = {
datePickerDialogFrom.show()
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth(),
) {
Text("Выбрать дату вылета")
}
Text(text = " ${departureDate.value?:""}", fontSize = 15.sp, color = Color.DarkGray)
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
navController.navigate(BottomBarScreen.FoundFlights.passText(from.value, to.value, departureDate.value))
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
content = {
Text(stringResource(id = R.string.button_search))
}
)
}
}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun LoadScreen(navController: NavHostController = rememberNavController(),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory),
userListViewModel: UserListViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketListViewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory),
rentListViewModel: RentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
Scaffold(
bottomBar = {BottomBar(navController = navController, currentUserViewModel) },
) {
Modifier
.padding(it)
HomeNavGraph(
navController = navController,
currentUserViewModel = currentUserViewModel,
flightListViewModel = flightListViewModel,
userListViewModel = userListViewModel,
ticketListViewModel = ticketListViewModel,
rentListViewModel = rentListViewModel
)
}
}

View File

@ -1,256 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.DismissValue
import androidx.compose.material3.DismissValue.DismissedToStart
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SwipeToDismiss
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
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.res.colorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.UsersRentsViewModel
@Composable
fun MyRents(
navController: NavController,
viewModel: UsersRentsViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
var userRentsUiState by remember { mutableStateOf(viewModel.userRentsUiState) }
SideEffect {
userRentsUiState = viewModel.userRentsUiState
}
Log.d("RentListViewModel", "мои бронирования: ${userRentsUiState.rentList}")
Scaffold(
) { innerPadding ->
MyRents(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
rentList = userRentsUiState.rentList,
onClick = { uid: Int ->
val route = BottomBarScreen.RentEdit.passId(uid.toString())
navController.navigate(route)
},
onSwipe = { rent: Rent ->
coroutineScope.launch {
viewModel.deleteRent(rent)
viewModel.refreshRents()
}
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
rentList: MutableList<Rent>,
rent: Rent,
onClick: (id: Int) -> Unit
) {
LaunchedEffect(dismissState.targetValue) {
if (dismissState.targetValue == DismissedToStart) {
rentList.remove(rent)
}
}
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = {
val backgroundColor by animateColorAsState(
when (dismissState.targetValue) {
DismissedToStart -> Color.Red.copy(alpha = 0.8f)
else -> Color.White
}, label = ""
)
val iconScale by animateFloatAsState(
targetValue = if (dismissState.targetValue == 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 = {
RentListItem(
rent = rent,
modifier = Modifier
.padding(vertical = 7.dp)
.clip(shape = RoundedCornerShape(15.dp))
.clickable { onClick(rent.id) }
)
}
)
}
//@OptIn(ExperimentalMaterial3Api::class)
//@Composable
//private fun SwipeToDelete(
// dismissState: DismissState,
// rent: Rent,
// onClick: (id: Int) -> Unit
//) {
// SwipeToDismiss(
// state = dismissState,
// directions = setOf(
// DismissDirection.EndToStart
// ),
// background = {
// val backgroundColor by animateColorAsState(
// when (dismissState.targetValue) {
// DismissedToStart -> Color.Red.copy(alpha = 0.8f)
// else -> Color.White
// }, label = ""
// )
// val iconScale by animateFloatAsState(
// targetValue = if (dismissState.targetValue == 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 = {
// RentListItem(rent = rent,
// modifier = Modifier
// .padding(vertical = 7.dp)
// .clip(shape = RoundedCornerShape(15.dp))
// .clickable { onClick(rent.id) })
// }
// )
//}
@SuppressLint("MutableCollectionMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MyRents(
modifier: Modifier = Modifier,
rentList: List<Rent>,
onClick: (id: Int) -> Unit,
onSwipe: (rent: Rent) -> Unit
) {
Column(
modifier = modifier.fillMaxSize()
) {
if (rentList.isEmpty()) {
Text(
text = "Бронирований пока нет",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
} else {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(items = rentList, key = { it.id }) { rent ->
val dismissState: DismissState = rememberDismissState(
positionalThreshold = { 200.dp.toPx() }
)
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
onSwipe(rent)
}
SwipeToDelete(
dismissState = dismissState,
rentList = rentList.toMutableList(),
rent = rent,
onClick = onClick
)
}
}
}
}
}
@Composable
private fun RentListItem(
modifier: Modifier = Modifier, rent: Rent
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = "Бронирование ${rent.id}"
)
}
}
}

View File

@ -1,173 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.util.Log
import androidx.compose.foundation.Image
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.stringResource
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 ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.graphs.AuthScreen
import ru.ulstu.`is`.airticketrentservice.graphs.Graph
import ru.ulstu.`is`.airticketrentservice.graphs.USERID_ARGUMENT
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.toUiState
@Composable
fun Profile(
navController: NavController,
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val currentUser by remember { mutableStateOf(currentUserViewModel.user) }
Log.d("CurrentUserViewModel1", "Текущий пользователь: $currentUser")
val state = rememberScrollState()
LaunchedEffect(Unit) { state.animateScrollTo(100) }
Column(
Modifier
.padding(all = 10.dp)
.fillMaxSize()
.navigationBarsPadding()
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp)
.verticalScroll(state)
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
Image(
bitmap = ImageBitmap.imageResource(R.drawable.logo),
contentDescription = "Логотип",
modifier = Modifier.size(100.dp, 100.dp)
)
Text(
text = stringResource(id = R.string.profile_heading),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
)
Divider(color = Color.Black, thickness = 2.dp,modifier = Modifier
.padding(bottom = 24.dp)
.fillMaxWidth())
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = "${currentUser?.surname} ${currentUser?.name} ${currentUser?.patronymic}", onValueChange = {}, readOnly = true,
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.name))
}
)
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = "${currentUser?.email}", onValueChange = {}, readOnly = true,
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.login_label))
}
)
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = "${currentUser?.date_of_birth}", onValueChange = {}, readOnly = true,
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.dateOfBirth))
}
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(5.dp),
onClick = {
navController.navigate(BottomBarScreen.UserEdit.passId(currentUser?.id.toString()))
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color.LightGray),
content = {
Text("Изменить")
}
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(5.dp),
onClick = {
val route = BottomBarScreen.MyRents.passId(currentUser?.id.toString())
navController.navigate(route)
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
content = {
Text("Мои бронирования")
}
)
}
}

View File

@ -1,127 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults.TrailingIcon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.RentDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.RentEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketsListUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.UserEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.UsersListUiState
@SuppressLint("UnrememberedMutableState")
@Composable
fun RentEdit(
viewModel: RentEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketViewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
ticketViewModel.setCurrentTicket(viewModel.rentUiState.rentDetails.ticketId)
RentEdit(
rentUiState = viewModel.rentUiState,
ticketUiState = ticketViewModel.ticketUiState
)
}
@Composable
private fun RentEdit(
rentUiState: RentUiState,
ticketUiState: TicketUiState
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
) {
TextField(
value = "${ticketUiState.ticketDetails.ticket_cost}",
onValueChange = {},
readOnly = true,
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text("Стоимость билета")
}
)
TextField(
value = "${ticketUiState.ticketDetails.passengers_count}",
onValueChange = {},
readOnly = true,
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text("Количество пассажиров")
}
)
TextField(
value = rentUiState.rentDetails.status,
onValueChange = {},
readOnly = true,
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.rent_status))
}
)
}
}

View File

@ -1,251 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeOut
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
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.outlined.Delete
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.DismissValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
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.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel
@Composable
fun RentList(
navController: NavController,
userListViewModel: UserListViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketListViewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory),
viewModel: RentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val rentListUiState = viewModel.rentListUiState.collectAsLazyPagingItems()
Scaffold(
) { innerPadding ->
RentList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
rentList = rentListUiState,
onClick = { uid: Int ->
val route = BottomBarScreen.RentStatusEdit.passId(uid.toString())
navController.navigate(route)
},
onSwipe = { rent: Rent ->
coroutineScope.launch {
viewModel.deleteRent(rent)
}
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DismissBackground(dismissState: DismissState) {
val color = when (dismissState.dismissDirection) {
DismissDirection.StartToEnd -> Color.Transparent
DismissDirection.EndToStart -> Color(0xFFFF1744)
null -> Color.Transparent
}
val direction = dismissState.dismissDirection
Row(
modifier = Modifier
.fillMaxSize()
.background(color)
.padding(12.dp, 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
if (direction == DismissDirection.EndToStart) {
Icon(
Icons.Default.Delete,
contentDescription = "delete",
tint = Color.White
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
rent: Rent,
onClick: (uid: Int) -> Unit
) {
SwipeToDismiss(
modifier = Modifier.zIndex(1f),
state = dismissState,
directions = setOf(
DismissDirection.EndToStart
),
background = {
DismissBackground(dismissState)
},
dismissContent = {
RentListItem(rent = rent,
modifier = Modifier
.padding(vertical = 7.dp)
.clickable { onClick(rent.id) })
}
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun RentList(
modifier: Modifier = Modifier,
rentList: LazyPagingItems<Rent>,
onClick: (uid: Int) -> Unit,
onSwipe: (rent: Rent) -> Unit
) {
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
rentList.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
Box(
modifier = modifier.pullRefresh(state)
) {
Column(
modifier = modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(
count = rentList.itemCount,
key = rentList.itemKey(),
contentType = rentList.itemContentType()
) { index ->
val rent = rentList[index]
rent?.let {
var show by remember { mutableStateOf(true) }
val dismissState = rememberDismissState(
confirmValueChange = {
if (it == DismissValue.DismissedToStart ||
it == DismissValue.DismissedToEnd
) {
show = false
true
} else false
}, positionalThreshold = { 200.dp.toPx() }
)
AnimatedVisibility(
show, exit = fadeOut(spring())
) {
SwipeToDelete(
dismissState = dismissState,
rent = rent,
onClick = onClick
)
}
LaunchedEffect(show) {
if (!show) {
delay(800)
onSwipe(rent)
}
}
}
}
}
PullRefreshIndicator(
refreshing, state,
Modifier
.align(CenterHorizontally)
.zIndex(100f)
)
}
}
}
@Composable
private fun RentListItem(
rent: Rent, modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = "Бронирование ${rent.id}"
)
}
}
}

View File

@ -1,200 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.RentDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.RentEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RentUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.UserEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserUiState
@SuppressLint("UnrememberedMutableState")
@Composable
fun RentStatusEdit(
navController: NavController,
viewModel: RentEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
userViewModel: UserEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
ticketViewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
userViewModel.setCurrentUser(viewModel.rentUiState.rentDetails.userId)
ticketViewModel.setCurrentTicket(viewModel.rentUiState.rentDetails.ticketId)
RentStatusEdit(
rentUiState = viewModel.rentUiState,
userUiState = userViewModel.userUiState,
ticketUiState = ticketViewModel.ticketUiState,
onClick = {
coroutineScope.launch {
viewModel.saveRent()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun RentStatusEdit(
rentUiState: RentUiState,
userUiState: UserUiState,
ticketUiState: TicketUiState,
onClick: () -> Unit,
onUpdate: (RentDetails) -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
) {
TextField(
value = rentUiState.rentDetails.ticketId.toString(),
onValueChange = { onUpdate(rentUiState.rentDetails.copy(ticketId = it.toInt())) },
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text("Номер билета")
}
)
TextField(
value = "${userUiState.userDetails.surname} ${userUiState.userDetails.name} ${userUiState.userDetails.patronymic}",
onValueChange = {},
readOnly = true,
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text("Пользователь")
}
)
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier.fillMaxWidth().padding(all = 5.dp).clip(RoundedCornerShape(15.dp))
) {
ExposedDropdownMenuBox(
modifier = Modifier.fillMaxWidth(),
expanded = expanded,
onExpandedChange = { expanded = !expanded },
)
{
TextField(
value = rentUiState.rentDetails.status,
onValueChange = {},
readOnly = true,
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
modifier = Modifier.menuAnchor().fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.LightGray.copy(.2f),
unfocusedContainerColor = Color.LightGray.copy(.2f),
disabledContainerColor = Color.LightGray.copy(.2f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.rent_status))
}
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.LightGray.copy(.2f))
.exposedDropdownSize()
.fillMaxWidth()
) {
DropdownMenuItem(
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(15.dp)),
text = { Text("Подтверждено") },
onClick = {
onUpdate(rentUiState.rentDetails.copy(status = "Подтверждено"))
expanded = false
}
)
DropdownMenuItem(
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(15.dp)),
text = { Text("Ожидает подтверждения") },
onClick = {
onUpdate(rentUiState.rentDetails.copy(status = "Ожидает подтверждения"))
expanded = false
}
)
DropdownMenuItem(
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(15.dp)),
text = { Text("Отклонено") },
onClick = {
onUpdate(rentUiState.rentDetails.copy(status = "Отклонено"))
expanded = false
}
)
}
}
}
Button(
enabled = rentUiState.isEntryValid,
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
onClick = onClick,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
content = {
Text(text = stringResource(R.string.rent_save_button))
}
)
}
}

View File

@ -1,246 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults.TrailingIcon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
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.graphics.Color
import androidx.compose.ui.res.colorResource
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 kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDropDownUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDropDownViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightsDropDownListUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.toUiState
@Composable
fun TicketEdit(
navController: NavController,
viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightViewModel: FlightDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
flightViewModel.setCurrentDropDownFlight(viewModel.ticketUiState.ticketDetails.flightId)
TicketEdit(
ticketUiState = viewModel.ticketUiState,
flightUiState = flightViewModel.flightDropDownUiState,
flightsListUiState = flightViewModel.flightsDropDownListUiState,
onClick = {
coroutineScope.launch {
viewModel.saveTicket()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
onFlightUpdate = flightViewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FlightDropDown(
flightUiState: FlightDropDownUiState,
flightsListUiState: FlightsDropDownListUiState,
onFlightUpdate: (Flight) -> Unit
) {
var expanded: Boolean by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
.padding(10.dp),
value = if (flightUiState.flight?.direction_from.isNullOrEmpty() || flightUiState.flight?.direction_to.isNullOrEmpty()) { "Рейс не выбран" }
else { "${flightUiState.flight?.direction_from} --> ${flightUiState.flight?.direction_to}" },
onValueChange = {},
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
trailingIcon = {
TrailingIcon(expanded = expanded)
},
shape = RoundedCornerShape(15.dp),
label = { Text("Рейс") }
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(colorResource(id = R.color.lightGray))
.clip(RoundedCornerShape(15.dp))
.exposedDropdownSize()
) {
flightsListUiState.flightList.forEach { flight ->
DropdownMenuItem(
text = {
Text("${flight.direction_from} --> ${flight.direction_to}")
},
onClick = {
onFlightUpdate(flight)
expanded = false
}
)
}
}
}
}
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TicketEdit(
ticketUiState: TicketUiState,
flightUiState: FlightDropDownUiState,
flightsListUiState: FlightsDropDownListUiState,
onClick: () -> Unit,
onUpdate: (TicketDetails) -> Unit,
onFlightUpdate: (Flight) -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
FlightDropDown(
flightUiState = flightUiState,
flightsListUiState = flightsListUiState
) {
onUpdate(ticketUiState.ticketDetails.copy(flightId = it.id))
onFlightUpdate(it)
}
val passengersCount by derivedStateOf {
ticketUiState.ticketDetails.passengers_count
}
val oneTicketCost by derivedStateOf {
flightUiState.flight?.one_ticket_cost ?: 0
}
val totalCost by derivedStateOf {
passengersCount * oneTicketCost.toDouble()
}
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = oneTicketCost.toString(),
onValueChange = { },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Стоимость одного билета") },
singleLine = true
)
val ticketCost = remember { mutableDoubleStateOf(totalCost) }
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = ticketUiState.ticketDetails.passengers_count.toString(),
onValueChange = {
if (it.isNotEmpty()) {
onUpdate(ticketUiState.ticketDetails.copy(passengers_count = it.toInt()))
} else {
onUpdate(ticketUiState.ticketDetails.copy(passengers_count = 0))
}},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Количество пассажиров") },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
LaunchedEffect(totalCost) {
ticketCost.doubleValue = totalCost
onUpdate(ticketUiState.ticketDetails.copy(ticket_cost = ticketCost.doubleValue))
}
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = ticketCost.doubleValue.toString(),
onValueChange = {
onUpdate(ticketUiState.ticketDetails.copy(ticket_cost = it.toDouble()))
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Стоимость билета") },
singleLine = true,
readOnly = true
)
Button(
onClick = onClick,
enabled = ticketUiState.isEntryValid,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Сохранить")
}
}
}

View File

@ -1,245 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeOut
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.DismissValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
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.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightListViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel
@Composable
fun TicketList(
navController: NavController,
flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory),
viewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val ticketListUiState = viewModel.ticketListUiState.collectAsLazyPagingItems()
Scaffold(
topBar = {},
floatingActionButton = {
FloatingActionButton(
onClick = {
val route = BottomBarScreen.TicketEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
},
containerColor = colorResource(R.color.lightlightBlue)
) {
Icon(Icons.Filled.Add, "Добавить")
}
}
) { innerPadding ->
TicketList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
ticketList = ticketListUiState,
onClick = { id: Int ->
val route = BottomBarScreen.TicketEdit.passId(id.toString())
navController.navigate(route)
},
onSwipe = { ticket: Ticket ->
coroutineScope.launch {
viewModel.deleteTicket(ticket)
}
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DismissBackground(dismissState: DismissState) {
val color = when (dismissState.dismissDirection) {
DismissDirection.StartToEnd -> Color.Transparent
DismissDirection.EndToStart -> Color(0xFFFF1744)
null -> Color.Transparent
}
val direction = dismissState.dismissDirection
Row(
modifier = Modifier
.fillMaxSize()
.background(color)
.padding(12.dp, 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
if (direction == DismissDirection.EndToStart) {
Icon(
Icons.Default.Delete,
contentDescription = "delete",
tint = Color.White
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
ticket: Ticket,
onClick: (id: Int) -> Unit
) {
SwipeToDismiss(
modifier = Modifier.zIndex(1f),
state = dismissState,
directions = setOf(
DismissDirection.EndToStart
),
background = {
DismissBackground(dismissState)
},
dismissContent = {
TicketListItem(ticket = ticket,
modifier = Modifier
.padding(vertical = 7.dp)
.clickable { onClick(ticket.id) })
}
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun TicketList(
modifier: Modifier = Modifier,
ticketList: LazyPagingItems<Ticket>,
onClick: (id: Int) -> Unit,
onSwipe: (ticket: Ticket) -> Unit
) {
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
ticketList.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
Box(
modifier = modifier.pullRefresh(state)
) {
Column(
modifier = modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(
count = ticketList.itemCount,
key = ticketList.itemKey(),
contentType = ticketList.itemContentType()
) { index ->
val ticket = ticketList[index]
ticket?.let {
var show by remember { mutableStateOf(true) }
val dismissState = rememberDismissState(
confirmValueChange = {
if (it == DismissValue.DismissedToStart ||
it == DismissValue.DismissedToEnd
) {
show = false
true
} else false
}, positionalThreshold = { 200.dp.toPx() }
)
AnimatedVisibility(
show, exit = fadeOut(spring())
) {
SwipeToDelete(
dismissState = dismissState,
ticket = ticket,
onClick = onClick
)
}
LaunchedEffect(show) {
if (!show) {
delay(800)
onSwipe(ticket)
}
}
}
}
}
PullRefreshIndicator(
refreshing, state,
Modifier
.align(Alignment.CenterHorizontally)
.zIndex(100f)
)
}
}
}
@Composable
private fun TicketListItem(
ticket: Ticket, modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = "Билет ${ticket.id}. Стоимость ${ticket.ticket_cost}"
)
}
}
}

View File

@ -1,248 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
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.graphics.Color
import androidx.compose.ui.res.colorResource
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 kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDropDownUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightDropDownViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.FlightsDropDownListUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.RentEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState
import ru.ulstu.`is`.airticketrentservice.viewModel.TicketViewViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.toDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.toTicket
@Composable
fun TicketView(
navController: NavController,
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory),
viewModel: TicketViewViewModel = viewModel(factory = AppViewModelProvider.Factory),
flightViewModel: FlightDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory),
rentEditViewModel: RentEditViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
flightViewModel.setCurrentDropDownFlight(viewModel.flightId)
val currentUser by remember { mutableStateOf(currentUserViewModel.user) }
TicketView(
ticketUiState = viewModel.ticketUiState,
flightUiState = flightViewModel.flightDropDownUiState,
onClick = {
coroutineScope.launch {
viewModel.saveNewTicket(viewModel.flightId)
viewModel.savedTicket.collect { id ->
currentUser?.let { id?.toInt()
?.let { it1 -> rentEditViewModel.saveNewRent(it.id, it1) } } }
}
navController.navigate(BottomBarScreen.Profile.route)
},
onUpdate = viewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FlightView(
flightUiState: FlightDropDownUiState
) {
flightUiState.flight?.direction_from?.let {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = it,
onValueChange = {},
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_from)) }
)
}
flightUiState.flight?.direction_to?.let {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = it,
onValueChange = {},
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_to)) }
)
}
flightUiState.flight?.departure_date?.let {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = it,
onValueChange = {},
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_arrivalDate)) }
)
}
flightUiState.flight?.arrival_date?.let {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = it,
onValueChange = {},
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.ticket_departureDate)) }
)
}
}
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TicketView(
ticketUiState: TicketUiState,
flightUiState: FlightDropDownUiState,
onClick: () -> Unit,
onUpdate: (TicketDetails) -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
FlightView(flightUiState = flightUiState)
val passengersCount by derivedStateOf {
ticketUiState.ticketDetails.passengers_count
}
val oneTicketCost by derivedStateOf {
flightUiState.flight?.one_ticket_cost ?: 0
}
val totalCost by derivedStateOf {
passengersCount * oneTicketCost.toDouble()
}
val ticketCost = remember { mutableDoubleStateOf(totalCost) }
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = ticketUiState.ticketDetails.passengers_count.toString(),
onValueChange = {
if (it.isNotEmpty()) {
onUpdate(ticketUiState.ticketDetails.copy(passengers_count = it.toInt()))
} else {
onUpdate(ticketUiState.ticketDetails.copy(passengers_count = 1))
}},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Количество пассажиров") },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
LaunchedEffect(totalCost) {
ticketCost.doubleValue = totalCost
onUpdate(ticketUiState.ticketDetails.copy(ticket_cost = ticketCost.doubleValue))
}
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
value = ticketCost.doubleValue.toString(),
onValueChange = {
onUpdate(ticketUiState.ticketDetails.copy(ticket_cost = it.toDouble()))
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Стоимость билета за всех пассажиров") },
singleLine = true,
readOnly = true
)
Button(
onClick = onClick,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Забронировать")
}
}
}

View File

@ -1,205 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
import android.app.DatePickerDialog
import android.widget.DatePicker
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.currentCompositionErrors
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.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
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 kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.UserDetails
import ru.ulstu.`is`.airticketrentservice.viewModel.UserEditViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.UserUiState
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import java.util.Calendar
import java.util.Date
@Composable
fun UserEdit(
navController: NavController,
viewModel: UserEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
UserEdit(
userUiState = viewModel.userUiState,
onClick = {
coroutineScope.launch {
viewModel.saveUser()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun UserEdit(
userUiState: UserUiState,
onClick: () -> Unit,
onUpdate: (UserDetails) -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
modifier = Modifier.fillMaxWidth().padding(10.dp),
value = userUiState.userDetails.surname,
onValueChange = { onUpdate(userUiState.userDetails.copy(surname = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Фамилия") },
singleLine = true
)
TextField(
modifier = Modifier.fillMaxWidth().padding(10.dp),
value = userUiState.userDetails.name,
onValueChange = { onUpdate(userUiState.userDetails.copy(name = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Имя") },
singleLine = true
)
TextField(
modifier = Modifier.fillMaxWidth().padding(10.dp),
value = userUiState.userDetails.patronymic,
onValueChange = { onUpdate(userUiState.userDetails.copy(patronymic = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Отчество") },
singleLine = true
)
val mContext = LocalContext.current
val year: Int
val month: Int
val day: Int
// Initializing a Calendar
val calendar = Calendar.getInstance()
// Fetching current year, month and day
year = calendar.get(Calendar.YEAR)
month = calendar.get(Calendar.MONTH)
day = calendar.get(Calendar.DAY_OF_MONTH)
calendar.time = Date()
// Declaring a string value to
// store date in string format
val date = remember { mutableStateOf("") }
// Declaring DatePickerDialog and setting
// initial values as current values (present year, month and day)
val datePickerDialog = DatePickerDialog(
mContext,
{ _: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
val selectedDate = "$dayOfMonth-${month + 1}-$year"
date.value = selectedDate
onUpdate(userUiState.userDetails.copy(date_of_birth = selectedDate))
}, year, month, day
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
onClick = {
datePickerDialog.show()
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth(),
) {
Text("Выбрать дату рождения")
}
Text(text = " ${userUiState.userDetails.date_of_birth}", fontSize = 15.sp, color = Color.DarkGray, textAlign = TextAlign.Center)
TextField(
modifier = Modifier.fillMaxWidth().padding(10.dp),
value = userUiState.userDetails.email,
onValueChange = { onUpdate(userUiState.userDetails.copy(email = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text("Электронная почта") },
singleLine = true
)
TextField(
modifier = Modifier.fillMaxWidth().padding(10.dp),
value = userUiState.userDetails.password,
onValueChange = { onUpdate(userUiState.userDetails.copy(password = it)) },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = { Text(stringResource(id = R.string.user_password)) },
singleLine = true
)
Button(
onClick = onClick,
enabled = userUiState.isEntryValid,
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
modifier = Modifier.fillMaxWidth().padding(10.dp)
) {
Text(text = stringResource(R.string.ticket_save_button))
}
}
}

View File

@ -1,143 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SwipeToDismiss
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel
@Composable
fun UserList(
navController: NavController,
viewModel: UserListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val userListUiState = viewModel.userListUiState
Scaffold(
) { innerPadding ->
UserList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
userList = userListUiState.userList,
onClick = { uid: Int ->
navController.navigate(BottomBarScreen.UserEdit.passId(uid.toString()))
},
onSwipe = { user: User ->
coroutineScope.launch {
viewModel.deleteUser(user)
}
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
user: User,
onClick: (id: Int) -> Unit
) {
SwipeToDismiss(
modifier = Modifier.zIndex(1f),
state = dismissState,
directions = setOf(),
background = {},
dismissContent = {
UserListItem(user = user,
modifier = Modifier
.padding(vertical = 7.dp)
.clickable { onClick(user.id) })
}
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun UserList(
modifier: Modifier = Modifier,
userList: List<User>,
onClick: (uid: Int) -> Unit,
onSwipe: (user: User) -> Unit
) {
Column(
modifier = modifier.fillMaxSize()
) {
if (userList.isEmpty()) {
Text(
text = "Пользователи не найдены",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
} else {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(items = userList, key = { it.id }) { user ->
val dismissState: DismissState = rememberDismissState(
positionalThreshold = { 200.dp.toPx() }
)
SwipeToDelete(
dismissState = dismissState,
user = user,
onClick = onClick
)
}
}
}
}
}
@Composable
private fun UserListItem(
user: User, modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(colorResource(id = R.color.lightlightBlue))
) {
Column(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = String.format("%s %s %s", user.surname, user.name, user.patronymic?: "")
)
}
}
}

View File

@ -1,199 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen.auth
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
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.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.elements.ValidateEmail
import ru.ulstu.`is`.airticketrentservice.elements.isValidEmail
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.LoginViewModel
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.graphs.AuthScreen
import ru.ulstu.`is`.airticketrentservice.graphs.Graph
import androidx.compose.material3.AlertDialog
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Login(navController: NavController, modifier: Modifier = Modifier, loginViewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var emailValue by rememberSaveable { mutableStateOf("") }
var passwordValue by rememberSaveable { mutableStateOf("") }
loginViewModel.setUserList()
val users = mutableStateOf<List<User>>(loginViewModel.userList)
val argument = currentUserViewModel.argument.value
var passwordVisibility by rememberSaveable { mutableStateOf(false) }
val emailRegex = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
val showInvalidPasswordDialog = remember { mutableStateOf(false) }
if (showInvalidPasswordDialog.value) {
AlertDialog(
onDismissRequest = { showInvalidPasswordDialog.value = false },
title = { Text(text = "Ошибка") },
text = { Text(text = "Такого пользователя не существует.") },
confirmButton = {
Button(
onClick = { showInvalidPasswordDialog.value = false },
) {
Text(text = "ОК")
}
}
)
}
Column(
Modifier
.padding(all = 10.dp)
.fillMaxSize()
.navigationBarsPadding()
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
Image(
bitmap = ImageBitmap.imageResource(R.drawable.logo),
contentDescription = "Логотип",
modifier = Modifier.size(100.dp, 100.dp)
)
Text(
text = stringResource(id = R.string.login_heading_text),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
)
Divider(color = Color.Black, thickness = 2.dp,modifier = Modifier
.padding(bottom = 24.dp)
.fillMaxWidth())
ValidateEmail(emailValue) { emailValue = it }
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = passwordValue, onValueChange = { passwordValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.password_label))
},
placeholder = { Text("password") },
singleLine = true,
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisibility)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
val description =
if (passwordVisibility) "Скрыть" else "Показать"
IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
Icon(imageVector = image, description)
}
}
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
enabled = (passwordValue.isNotEmpty() && isValidEmail(emailValue)),
shape = RoundedCornerShape(15.dp),
onClick = {
if (passwordValue.isNotEmpty() && isValidEmail(emailValue)) {
var userExists = false
users.value.forEach { user ->
if (user.password == passwordValue && user.email == emailValue) {
currentUserViewModel.setArgument(user.id.toString())
navController.navigate(route = Graph.passUserId(user.id.toString()))
Log.d("CurrentUserViewModel", "Текущий пользователь: $user")
userExists = true
}
}
if (!userExists) {
showInvalidPasswordDialog.value = true
}
}
},
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
content = {
Text(stringResource(id = R.string.login_button))
}
)
Text(
text = "Ещё не зарегистрированы?",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth(),
)
TextButton(
modifier = Modifier
.fillMaxWidth(),
onClick = {
navController.navigate(route = AuthScreen.Registration.route)
},
content = {
Text(
stringResource(id = R.string.registration),
color = colorResource(R.color.lightBlue), textDecoration = TextDecoration.Underline)
}
)
}
}

View File

@ -1,358 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.screen.auth
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.util.Log
import android.widget.DatePicker
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
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.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.elements.isValidEmail
import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider
import ru.ulstu.`is`.airticketrentservice.viewModel.CurrentUserViewModel
import ru.ulstu.`is`.airticketrentservice.viewModel.RegistrationViewModel
import ru.ulstu.`is`.airticketrentservice.R
import ru.ulstu.`is`.airticketrentservice.database.models.RoleEnum
import ru.ulstu.`is`.airticketrentservice.elements.ValidateEmail
import ru.ulstu.`is`.airticketrentservice.graphs.AuthScreen
import ru.ulstu.`is`.airticketrentservice.graphs.Graph
import java.util.Calendar
import java.util.Date
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Registration(navController: NavController, modifier: Modifier = Modifier, registrationViewModel: RegistrationViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var emailValue by rememberSaveable { mutableStateOf("") }
var passwordValue by rememberSaveable { mutableStateOf("") }
var surnameValue by rememberSaveable { mutableStateOf("") }
var nameValue by rememberSaveable { mutableStateOf("") }
var patronymicValue by rememberSaveable { mutableStateOf("") }
var dateOfBirthValue by rememberSaveable { mutableStateOf("") }
val coroutineScope = rememberCoroutineScope()
registrationViewModel.setUserList()
val users = mutableStateOf<List<User>>(emptyList())
registrationViewModel.users.observeForever { userList ->
users.value = userList
}
var isValid = "Данные корректны"
val state = rememberScrollState()
LaunchedEffect(Unit) { state.animateScrollTo(100) }
var passwordVisibility by rememberSaveable { mutableStateOf(false) }
val emailRegex = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
Column(
Modifier
.padding(all = 10.dp)
.fillMaxSize()
.navigationBarsPadding()
.padding(horizontal = 24.dp)
.padding(vertical = 32.dp)
.verticalScroll(state),
horizontalAlignment = Alignment.CenterHorizontally) {
Image(
bitmap = ImageBitmap.imageResource(R.drawable.logo),
contentDescription = "Логотип",
modifier = Modifier.size(100.dp, 100.dp)
)
Text(
text = stringResource(id = R.string.registration),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
)
Divider(color = Color.Black, thickness = 2.dp,modifier = Modifier
.padding(bottom = 24.dp)
.fillMaxWidth())
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = surnameValue, onValueChange = { surnameValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.surname))
}
)
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = nameValue, onValueChange = { nameValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.name))
}
)
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = patronymicValue, onValueChange = { patronymicValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = {
Text("Отчество")
}
)
val mContext = LocalContext.current
val year: Int
val month: Int
val day: Int
val calendar = Calendar.getInstance()
year = calendar.get(Calendar.YEAR)
month = calendar.get(Calendar.MONTH)
day = calendar.get(Calendar.DAY_OF_MONTH)
calendar.time = Date()
val date = remember { mutableStateOf("") }
val datePickerDialog = DatePickerDialog(
mContext,
{ _: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
val selectedDate = "$dayOfMonth-${month + 1}-$year"
date.value = selectedDate
dateOfBirthValue = selectedDate
}, year, month, day
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = (colorResource(id = R.color.lightBlue)),
contentColor = Color.White
),
onClick = {
datePickerDialog.show()
},
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth(),
) {
Text("Выбрать дату рождения")
}
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = dateOfBirthValue, onValueChange = { dateOfBirthValue = it }, readOnly = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.dateOfBirth))
}
)
ValidateEmail(emailValue) { emailValue = it }
TextField(modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
value = passwordValue, onValueChange = { passwordValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.LightGray.copy(.2f),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(15.dp),
label = {
Text(stringResource(id = R.string.password_label))
},
singleLine = true,
placeholder = { Text(stringResource(id = R.string.password_label)) },
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisibility)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
val description =
if (passwordVisibility) "Скрыть" else "Показать"
IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
Icon(imageVector = image, description)
}
}
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
onClick = {
var isExist = false;
if (passwordValue.isNotEmpty() && isValidEmail(emailValue) && surnameValue.isNotEmpty() && nameValue.isNotEmpty() && patronymicValue.isNotEmpty() && dateOfBirthValue.isNotEmpty()) {
users.value.forEach { user ->
if (user.email == emailValue) {
Log.d("User already exist. User id: ", user.id.toString())
isExist = true
}
}
if (!isExist) {
val newUser = User(
0,
surnameValue,
nameValue,
patronymicValue,
dateOfBirthValue,
emailValue,
passwordValue,
RoleEnum.User.name
)
coroutineScope.launch {
val insertResult = async {
registrationViewModel.insertUser(newUser)
}
insertResult.await()
registrationViewModel.setUserList()
registrationViewModel.users.observeForever { userList ->
users.value = userList
Log.println(Log.ASSERT, "UsersList", users.value.toString())
users.value.forEach { user ->
if (user.password == passwordValue && user.email == emailValue) {
currentUserViewModel.setArgument(user.id.toString())
navController.navigate(route = Graph.passUserId(user.id.toString()))
}
}
}
}
}
}
},
enabled = (passwordValue.isNotEmpty() && isValidEmail(emailValue) && surnameValue.isNotEmpty() && nameValue.isNotEmpty() && patronymicValue.isNotEmpty() && dateOfBirthValue.isNotEmpty()),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)),
content = {
Text(stringResource(id = R.string.registration_button))
}
)
Text(
text = "Уже зарегистрированы?",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth(),
)
TextButton(
modifier = Modifier
.fillMaxWidth(),
onClick = {
navController.navigate(route = AuthScreen.Login.route)
},
content = {
Text(
stringResource(id = R.string.login_heading_text),
color = colorResource(R.color.lightBlue), textDecoration = TextDecoration.Underline)
}
)
}
}
@Composable
fun AlertDialog(
showDialog: Boolean,
onDismiss: () -> Unit,
text: String = ""
) {
if (showDialog) {
AlertDialog (
onDismissRequest = { onDismiss() },
title = {
Text(text)
},
confirmButton = {
Button(onClick = { onDismiss() }, elevation = ButtonDefaults.buttonElevation(
defaultElevation = 10.dp,
pressedElevation = 6.dp
),
shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue))) {
Text("OK")
}
}
)
}
}

View File

@ -1,11 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.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)

View File

@ -1,70 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun AirTicketRentServiceTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -1,34 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -1,84 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import ru.ulstu.`is`.airticketrentservice.TicketApplication
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
CurrentUserViewModel(ticketApplication().container.userRestRepository)
}
initializer {
LoginViewModel(ticketApplication().container.userRestRepository)
}
initializer {
RegistrationViewModel(ticketApplication().container.userRestRepository)
}
initializer {
FlightListViewModel(ticketApplication().container.flightRestRepository)
}
initializer {
UserListViewModel(ticketApplication().container.userRestRepository)
}
initializer {
TicketListViewModel(ticketApplication().container.ticketRestRepository)
}
initializer {
RentListViewModel(ticketApplication().container.rentRestRepository)
}
initializer {
TicketEditViewModel(
this.createSavedStateHandle(),
ticketApplication().container.ticketRestRepository
)
}
initializer {
TicketViewViewModel(
this.createSavedStateHandle(),
ticketApplication().container.ticketRestRepository
)
}
initializer {
RentEditViewModel(
this.createSavedStateHandle(),
ticketApplication().container.rentRestRepository
)
}
initializer {
UsersRentsViewModel(
this.createSavedStateHandle(),
ticketApplication().container.userRestRepository,
ticketApplication().container.rentRestRepository
)
}
initializer {
FindFlightsViewModel(
this.createSavedStateHandle(),
ticketApplication().container.flightRestRepository)
}
initializer {
UserEditViewModel(
this.createSavedStateHandle(),
ticketApplication().container.userRestRepository
)
}
initializer {
FlightEditViewModel(
this.createSavedStateHandle(),
ticketApplication().container.flightRestRepository
)
}
initializer {
FlightDropDownViewModel(
ticketApplication().container.flightRestRepository
)
}
}
}
fun CreationExtras.ticketApplication(): TicketApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as TicketApplication)

View File

@ -1,25 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class CurrentUserViewModel(private val userRepository: UserRepository) : ViewModel(){
val argument = mutableStateOf<String?>(null)
private val userid = mutableIntStateOf(0)
var user by mutableStateOf<User?>(null)
fun setArgument(arg: String) {
argument.value = arg
userid.intValue = arg.toInt()
viewModelScope.launch {
user = userRepository.getUserById(userid.intValue)
}
}
}

View File

@ -1,36 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository
class FindFlightsViewModel(
savedStateHandle: SavedStateHandle,
private val flightRepository: FlightRepository
) : ViewModel() {
private val from: String = checkNotNull(savedStateHandle["direction_from"])
private val to: String = checkNotNull(savedStateHandle["direction_to"])
private val departureDate: String = checkNotNull(savedStateHandle["departure_date"])
var foundFlightsUiState by mutableStateOf(FoundFlightsUiState())
private set
init {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val flights = flightRepository.findFlights(from, to, departureDate)
launch(Dispatchers.Main) {
foundFlightsUiState = FoundFlightsUiState(flights)
}
}
}
}
}
data class FoundFlightsUiState(val flightList: List<Flight> = listOf())

View File

@ -1,53 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository
class FlightDropDownViewModel(
private val flightRepository: FlightRepository
) : ViewModel() {
var flightsDropDownListUiState by mutableStateOf(FlightsDropDownListUiState())
private set
var flightDropDownUiState by mutableStateOf(FlightDropDownUiState())
private set
init {
viewModelScope.launch {
flightsDropDownListUiState = FlightsDropDownListUiState(flightRepository.getAllFlights())
}
}
fun setCurrentDropDownFlight(flightId: Int) {
val flight: Flight? =
flightsDropDownListUiState.flightList.firstOrNull { flight -> flight.id == flightId }
flight?.let { updateUiState(it) }
}
fun updateUiState(flight: Flight) {
flightDropDownUiState = FlightDropDownUiState(
flight = flight
)
}
}
data class FlightsDropDownListUiState(val flightList: List<Flight> = listOf())
data class FlightDropDownUiState(
val flight: Flight? = null
)
fun Flight.toUiState() = FlightDropDownUiState(flight = Flight(
id = id,
direction_from = direction_from,
direction_to = direction_to,
departure_date = departure_date,
arrival_date = arrival_date,
one_ticket_cost = one_ticket_cost
))

View File

@ -1,102 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
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 androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository
class FlightEditViewModel(
savedStateHandle: SavedStateHandle,
private val flightRepository: FlightRepository
) : ViewModel() {
var flightUiState by mutableStateOf(FlightUiState())
private set
private val flightUid: Int = checkNotNull(savedStateHandle["id"])
val flightId = flightUid
init {
viewModelScope.launch {
if (flightUid > 0) {
flightUiState = flightRepository.getFlightById(flightUid)
.toUiState(true)
}
}
}
fun updateUiState(flightDetails: FlightDetails) {
flightUiState = FlightUiState(
flightDetails = flightDetails,
isEntryValid = validateInput(flightDetails),
flight = flightDetails.toFlight()
)
}
suspend fun saveFlight() {
if (validateInput()) {
if (flightUid > 0) {
flightRepository.updateFlight(
flightUiState.flightDetails.toFlight(flightUid)
)
} else {
flightRepository.insertFlight(
flightUiState.flightDetails.toFlight()
)
}
}
}
private fun validateInput(uiState: FlightDetails = flightUiState.flightDetails): Boolean {
return with(uiState) {
direction_from.isNotBlank()
&& direction_to.isNotBlank()
&& departure_date.isNotBlank()
&& arrival_date.isNotBlank()
&& one_ticket_cost > 0
}
}
}
data class FlightUiState(
val flightDetails: FlightDetails = FlightDetails(),
val isEntryValid: Boolean = false,
val flight: Flight? = null
)
data class FlightDetails(
val direction_from: String = "",
val direction_to: String = "",
val departure_date: String = "",
val arrival_date: String = "",
val one_ticket_cost: Double = 0.0
)
fun FlightDetails.toFlight(id: Int = 0): Flight = Flight(
id = id,
direction_from = direction_from,
direction_to = direction_to,
departure_date = departure_date,
arrival_date = arrival_date,
one_ticket_cost = one_ticket_cost
)
fun Flight.toDetails(): FlightDetails = FlightDetails(
direction_from = direction_from,
direction_to = direction_to,
departure_date = departure_date,
arrival_date = arrival_date,
one_ticket_cost = one_ticket_cost
)
fun Flight.toUiState(isEntryValid: Boolean = false): FlightUiState = FlightUiState(
flightDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -1,20 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.repository.FlightRepository
class FlightListViewModel(
private val flightRepository: FlightRepository
): ViewModel() {
var flightsListUiState: Flow<PagingData<Flight>> = flightRepository.getFlights()
fun deleteFlight(flight: Flight) = viewModelScope.launch {
flightRepository.deleteFlight(flight)
}
}

View File

@ -1,20 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class LoginViewModel(private val userRepository: UserRepository) : ViewModel() {
var userList by mutableStateOf<List<User>>(emptyList())
fun setUserList() {
viewModelScope.launch {
userList=userRepository.getAllUsers()
}
}
}

View File

@ -1,25 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class RegistrationViewModel(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

@ -1,103 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.repository.RentRepository
class RentEditViewModel(
savedStateHandle: SavedStateHandle,
private val rentRepository: RentRepository
) : ViewModel() {
var rentUiState by mutableStateOf(RentUiState())
private set
private val rentUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
val rent = if (rentUid > 0) {
withContext(Dispatchers.IO) {
rentRepository.getRentById(rentUid).toUiState(true)
}
} else {
null
}
if (rent != null) {
rentUiState = rent
}
}
}
fun updateUiState(rentDetails: RentDetails) {
rentUiState = RentUiState(
rentDetails = rentDetails,
isEntryValid = validateInput(rentDetails)
)
}
suspend fun saveRent() {
if (validateInput()) {
if (rentUid > 0) {
rentRepository.updateRent(rentUiState.rentDetails.toRent(rentUid))
} else {
rentRepository.insertRent(rentUiState.rentDetails.toRent())
}
}
}
suspend fun saveNewRent(userId: Int = 0, ticketId: Int = 0) {
val newRent = rentUiState.rentDetails.toNewRent(0, userId, ticketId)
rentRepository.insertRent(newRent)
}
private fun RentDetails.toNewRent(id: Int = 0, userId: Int = 0, ticketId: Int = 0): Rent = Rent(
id = id,
status = status,
userId = userId,
ticketId = ticketId
)
private fun validateInput(uiState: RentDetails = rentUiState.rentDetails): Boolean {
return with(uiState) {
status.isNotBlank()
&& userId > 0
&& ticketId > 0
}
}
}
data class RentUiState(
val rentDetails: RentDetails = RentDetails(),
val isEntryValid: Boolean = false
)
data class RentDetails(
val status: String = "Ожидает подтверждения",
val userId: Int = 0,
val ticketId: Int = 0
)
fun RentDetails.toRent(id: Int = 0): Rent = Rent(
id = id,
status = status,
userId = userId,
ticketId = ticketId
)
fun Rent.toDetails(): RentDetails = RentDetails(
status = status,
userId = userId,
ticketId = ticketId
)
fun Rent.toUiState(isEntryValid: Boolean = false): RentUiState = RentUiState(
rentDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -1,29 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.repository.RentRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class RentListViewModel (
private val rentRepository: RentRepository
) : ViewModel() {
val rentListUiState: Flow<PagingData<Rent>> = rentRepository.getRents()
suspend fun deleteRent(rent: Rent) {
rentRepository.deleteRent(rent)
}
}

View File

@ -1,123 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.repository.TicketRepository
class TicketEditViewModel(
savedStateHandle: SavedStateHandle,
private val ticketRepository: TicketRepository,
) : ViewModel() {
var ticketsListUiState by mutableStateOf(TicketsListUiState())
private set
var ticketUiState by mutableStateOf(TicketUiState())
private set
private val ticketUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
val tickets = withContext(Dispatchers.IO) {
ticketRepository.getAllTickets()
}
val ticket = if (ticketUid > 0) {
withContext(Dispatchers.IO) {
ticketRepository.getTicketById(ticketUid).toUiState(true)
}
} else {
null
}
ticketsListUiState = TicketsListUiState(tickets)
if (ticket != null) {
ticketUiState = ticket
}
}
}
fun setCurrentTicket(ticketId: Int) {
val ticket: Ticket? =
ticketsListUiState.ticketList.firstOrNull { ticket -> ticket.id == ticketId }
ticket?.let { updateUiState(it.toDetails()) }
}
fun updateUiState(ticketDetails: TicketDetails) {
ticketUiState = TicketUiState(
ticketDetails = ticketDetails,
isEntryValid = validateInput(ticketDetails),
ticket = ticketDetails.toTicket()
)
}
suspend fun saveTicket() {
if (validateInput()) {
if (ticketUid > 0) {
ticketRepository.updateTicket(
ticketUiState.ticketDetails.toTicket(ticketUid)
)
} else {
ticketRepository.insertTicket(
ticketUiState.ticketDetails.toTicket()
)
}
}
}
private fun validateInput(uiState: TicketDetails = ticketUiState.ticketDetails): Boolean {
return with(uiState) {
passengers_count > 0
&& ticket_cost > 0
&& flightId > 0
}
}
}
data class TicketUiState(
val ticketDetails: TicketDetails = TicketDetails(),
val isEntryValid: Boolean = false,
val ticket: Ticket? = null
)
data class TicketDetails(
val passengers_count: Int = 0,
var ticket_cost: Double = 0.0,
var flightId: Int = 0
)
fun TicketDetails.toTicket(uid: Int = 0): Ticket = Ticket(
id = uid,
passengers_count = passengers_count,
ticket_cost = ticket_cost,
flightId = flightId
)
fun TicketDetails.toNewTicket(uid: Int = 0, flightId: Int = 0): Ticket = Ticket(
id = uid,
passengers_count = passengers_count,
ticket_cost = ticket_cost,
flightId = flightId
)
fun Ticket.toDetails(): TicketDetails = TicketDetails(
passengers_count = passengers_count,
ticket_cost = ticket_cost,
flightId = flightId
)
fun Ticket.toUiState(isEntryValid: Boolean = false): TicketUiState = TicketUiState(
ticketDetails = this.toDetails(),
isEntryValid = isEntryValid
)
data class TicketsListUiState(val ticketList: List<Ticket> = listOf())

View File

@ -1,22 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.repository.TicketRepository
class TicketListViewModel(
private val ticketRepository: TicketRepository
): ViewModel() {
val ticketListUiState: Flow<PagingData<Ticket>> = ticketRepository.getTickets()
suspend fun deleteTicket(ticket: Ticket) {
ticketRepository.deleteTicket(ticket)
}
}

View File

@ -1,70 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.Ticket
import ru.ulstu.`is`.airticketrentservice.database.repository.TicketRepository
class TicketViewViewModel(
savedStateHandle: SavedStateHandle,
private val ticketRepository: TicketRepository,
) : ViewModel() {
var ticketUiState by mutableStateOf(TicketUiState())
private set
private val ticketUid: Int = checkNotNull(savedStateHandle["id"])
private val flightUid: Int = checkNotNull(savedStateHandle["flightId"])
val flightId = flightUid
val id = ticketUid
init {
viewModelScope.launch {
val ticket = if (ticketUid > 0) {
withContext(Dispatchers.IO) {
ticketRepository.getTicketById(ticketUid).toUiState(true)
}
} else {
null
}
if (ticket != null) {
ticketUiState = ticket
}
}
}
fun updateUiState(ticketDetails: TicketDetails) {
ticketUiState = TicketUiState(
ticketDetails = ticketDetails,
isEntryValid = validateInput(ticketDetails),
ticket = ticketDetails.toTicket()
)
}
val savedTicket: MutableStateFlow<Long?> = MutableStateFlow(null)
suspend fun saveNewTicket(flightId: Int) {
val newTicket = ticketUiState.ticketDetails.toNewTicket(0, flightId)
savedTicket.value = ticketRepository.insertTicket(newTicket)
}
private fun validateInput(uiState: TicketDetails = ticketUiState.ticketDetails): Boolean {
return with(uiState) {
passengers_count > 0
&& ticket_cost > 0
&& flightId > 0
}
}
}

View File

@ -1,121 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
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 androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class UserEditViewModel(
savedStateHandle: SavedStateHandle,
private val userRepository: UserRepository
) : ViewModel() {
var usersListUiState by mutableStateOf(UsersListUiState())
private set
var userUiState by mutableStateOf(UserUiState())
private set
private val userUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (userUid > 0) {
usersListUiState = UsersListUiState(userRepository.getAllUsers())
userUiState = userRepository.getUserById(userUid)
.toUiState(true)!!
}
}
}
fun setCurrentUser(userId: Int) {
val user: User? =
usersListUiState.userList.firstOrNull { user -> user.id == userId }
user?.let { it.toDetails()?.let { it1 -> updateUiState(it1) } }
}
fun updateUiState(userDetails: UserDetails) {
userUiState = UserUiState(
userDetails = userDetails,
isEntryValid = validateInput(userDetails),
user = userDetails.toUser()
)
}
suspend fun saveUser() {
if (validateInput()) {
if (userUid > 0) {
userRepository.updateUser(userUiState.userDetails.toUser(userUid))
} else {
userRepository.insertUser(userUiState.userDetails.toUser())
}
}
}
private fun validateInput(uiState: UserDetails = userUiState.userDetails): Boolean {
return with(uiState) {
surname.isNotBlank()
&& name.isNotBlank()
&& patronymic.isNotBlank()
&& date_of_birth.isNotBlank()
&& email.isNotBlank()
&& password.isNotBlank()
&& role.isNotBlank()
}
}
}
data class UserUiState(
val userDetails: UserDetails = UserDetails(),
val isEntryValid: Boolean = false,
val user: User? = null
)
data class UserDetails(
val surname: String = "",
val name: String = "",
val patronymic: String = "",
val date_of_birth: String = "",
val email: String = "",
val password: String = "",
val role: String = ""
)
fun UserDetails.toUser(id: Int = 0): User = User(
id = id,
surname = surname,
name = name,
patronymic = patronymic,
date_of_birth = date_of_birth,
email = email,
password = password,
role = role
)
fun User.toDetails(): UserDetails? = patronymic?.let {
UserDetails(
surname = surname,
name = name,
patronymic = it,
date_of_birth = date_of_birth,
email = email,
password = password,
role = role
)
}
fun User.toUiState(isEntryValid: Boolean = false): UserUiState? = this.toDetails()?.let {
UserUiState(
userDetails = it,
isEntryValid = isEntryValid
)
}
data class UsersListUiState(val userList: List<User> = listOf())

View File

@ -1,33 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.airticketrentservice.database.models.Flight
import ru.ulstu.`is`.airticketrentservice.database.models.User
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class UserListViewModel(
private val userRepository: UserRepository
) : ViewModel() {
var userListUiState by mutableStateOf(UserListUiState())
private set
init {
viewModelScope.launch {
userListUiState = UserListUiState(userRepository.getAllUsers())
}
}
suspend fun deleteUser(user: User) {
userRepository.deleteUser(user)
}
}
data class UserListUiState(val userList: List<User> = listOf())

View File

@ -1,60 +0,0 @@
package ru.ulstu.`is`.airticketrentservice.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.ulstu.`is`.airticketrentservice.database.AppContainer
import ru.ulstu.`is`.airticketrentservice.database.AppDataContainer
import ru.ulstu.`is`.airticketrentservice.database.models.Rent
import ru.ulstu.`is`.airticketrentservice.database.repository.RentRepository
import ru.ulstu.`is`.airticketrentservice.database.repository.UserRepository
class UsersRentsViewModel(
savedStateHandle: SavedStateHandle,
private val userRepository: UserRepository,
private val rentRepository: RentRepository
) : ViewModel() {
var userRentsUiState by mutableStateOf(UserRentsUiState())
private set
private val userUid: Int = checkNotNull(savedStateHandle["userId"])
init {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val rents = userRepository.getUserRents(userUid)
launch(Dispatchers.Main) {
if (userUid > 0) {
userRentsUiState = UserRentsUiState(rents)
}
}
}
}
}
fun refreshRents() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val rents = userRepository.getUserRents(userUid)
launch(Dispatchers.Main) {
userRentsUiState = UserRentsUiState(rents)
}
}
}
}
suspend fun deleteRent(rent: Rent) {
rentRepository.deleteRent(rent)
}
}
data class UserRentsUiState(val rentList: List<Rent> = listOf())

View File

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

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