diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7d8711 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.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/ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..585020d --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,101 @@ +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") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/ru/ulstu/is/airticketrentservice/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ru/ulstu/is/airticketrentservice/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..76e76bf --- /dev/null +++ b/app/src/androidTest/java/ru/ulstu/is/airticketrentservice/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +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) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b245d4c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/MainActivity.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/MainActivity.kt new file mode 100644 index 0000000..0285f51 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/MainActivity.kt @@ -0,0 +1,42 @@ +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() { +// @RequiresApi(Build.VERSION_CODES.O) +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// setContent { +// AirTicketRentServiceTheme { +// Surface( +// modifier = Modifier.fillMaxSize(), +// color = MaterialTheme.colorScheme.background +// ) { +// MainNavbar() +// } +// } +// } +// } + 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)) + } + } + } +} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/TicketApplication.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/TicketApplication.kt new file mode 100644 index 0000000..3d39116 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/TicketApplication.kt @@ -0,0 +1,14 @@ +package ru.ulstu.`is`.airticketrentservice + +import android.app.Application +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) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/AppService.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/AppService.kt new file mode 100644 index 0000000..540fc8c --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/AppService.kt @@ -0,0 +1,152 @@ +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 + @GET("rents") + suspend fun getRents( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + @GET("findFlights/{direction_from}-{direction_to}-{departure_date}") + suspend fun findFlights( + @Path("direction_from") from: String, + @Path("direction_to") to: String, + @Path("departure_date") departureDate: String + ): List + @GET("tickets") + suspend fun getAllTickets(): List + @GET("tickets") + suspend fun getTickets( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + @GET("flights") + suspend fun getFlights( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + + @GET("tickets") + suspend fun getFlightsTickets(@Query("flightId") flightId: Int): List + + @GET("userrents/{userId}") + suspend fun getUserRents(@Path("userId") userId: Int): List + + @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 } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/FlightRemoteMediator.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/FlightRemoteMediator.kt new file mode 100644 index 0000000..93d87ca --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/FlightRemoteMediator.kt @@ -0,0 +1,107 @@ +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() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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): 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): 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 + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { flightid -> + dbRemoteKeyRepository.getAllRemoteKeys(flightid, RemoteKeyType.FLIGHT) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/RentRemoteMediator.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/RentRemoteMediator.kt new file mode 100644 index 0000000..c88726e --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/RentRemoteMediator.kt @@ -0,0 +1,109 @@ +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.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 ticketRestRepository: RestTicketRepository, + private val database: AppDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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 + ) + } + dbRemoteKeyRepository.createRemoteKeys(keys) + ticketRestRepository.getTickets() + 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): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { rent -> + dbRemoteKeyRepository.getAllRemoteKeys(rent.id, RemoteKeyType.RENT) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { rent -> + dbRemoteKeyRepository.getAllRemoteKeys(rent.id, RemoteKeyType.RENT) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { rentUid -> + dbRemoteKeyRepository.getAllRemoteKeys(rentUid, RemoteKeyType.RENT) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/TicketRemoteMediator.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/TicketRemoteMediator.kt new file mode 100644 index 0000000..d8bd85b --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/mediator/TicketRemoteMediator.kt @@ -0,0 +1,110 @@ +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.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() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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.getFlights() + 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): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { ticket -> + dbRemoteKeyRepository.getAllRemoteKeys(ticket.id, RemoteKeyType.TICKET) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { ticket -> + dbRemoteKeyRepository.getAllRemoteKeys(ticket.id, RemoteKeyType.TICKET) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { ticketUid -> + dbRemoteKeyRepository.getAllRemoteKeys(ticketUid, RemoteKeyType.TICKET) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/FlightRemote.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/FlightRemote.kt new file mode 100644 index 0000000..821e770 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/FlightRemote.kt @@ -0,0 +1,35 @@ +package ru.ulstu.`is`.airticketrentservice.api.model + +import kotlinx.serialization.Serializable +import ru.ulstu.`is`.airticketrentservice.database.models.Flight + +@Serializable +data class FlightRemote( + val id: Int = 0, + var direction_from: String = "", + var direction_to: String = "", + var departure_date: String = "", + var arrival_date: String = "", + var tickets_count: Int = 0, + val one_ticket_cost: Double = 0.0 +) + +fun FlightRemote.toFlight(): Flight = Flight( + id, + direction_from, + direction_to, + departure_date, + arrival_date, + tickets_count, + one_ticket_cost +) + +fun Flight.toFlightRemote(): FlightRemote = FlightRemote( + id, + direction_from, + direction_to, + departure_date, + arrival_date, + tickets_count, + one_ticket_cost +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/RentRemote.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/RentRemote.kt new file mode 100644 index 0000000..b8cf57f --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/RentRemote.kt @@ -0,0 +1,26 @@ +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 +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/TicketRemote.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/TicketRemote.kt new file mode 100644 index 0000000..a04ace6 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/TicketRemote.kt @@ -0,0 +1,26 @@ +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 +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/UserRemote.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/UserRemote.kt new file mode 100644 index 0000000..5b5c2dd --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/model/UserRemote.kt @@ -0,0 +1,38 @@ +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 +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestFlightRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestFlightRepository.kt new file mode 100644 index 0000000..634cabe --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestFlightRepository.kt @@ -0,0 +1,118 @@ +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 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 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> { + 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 = suspendCancellableCoroutine { continuation -> +// val call = service.findFlights(from, to, departureDate) +// call.enqueue(object: Callback> { +// override fun onResponse(call: Call>, response: Response>) { +// if (response.isSuccessful) { +// val flightRemoteList = response.body() ?: emptyList() +// val flightList = flightRemoteList.map { it.toFlight() } +// continuation.resume(flightList) +// } else { +// continuation.resumeWithException(Exception("Server error: ${response.code()}")) +// } +// } +// +// override fun onFailure(call: Call>, t: Throwable) { +// continuation.resumeWithException(t) +// } +// }) +// +// continuation.invokeOnCancellation { +// call.cancel() +// } +// } + + override suspend fun findFlights( + from: String, + to: String, + departureDate: String + ): List { + 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 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestRentRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestRentRepository.kt new file mode 100644 index 0000000..6d0b1ca --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestRentRepository.kt @@ -0,0 +1,81 @@ +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 ticketRestRepository: RestTicketRepository, + private val database: AppDatabase +) : RentRepository { + override fun getRents(): Flow> { + 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, + 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 { +// 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 } +// } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestTicketRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestTicketRepository.kt new file mode 100644 index 0000000..8a3bc63 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestTicketRepository.kt @@ -0,0 +1,99 @@ +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> { + 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 { + 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) { + service.createTicket(ticket.toTicketRemote()).toTicket() + } + + 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 { + 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 } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestUserRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestUserRepository.kt new file mode 100644 index 0000000..b22dfe4 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/api/repository/RestUserRepository.kt @@ -0,0 +1,81 @@ +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.toRent +import ru.ulstu.`is`.airticketrentservice.api.model.toUser +import ru.ulstu.`is`.airticketrentservice.api.model.toUserRemote +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 { + 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(id: Int): List { + Log.d(RestUserRepository::class.simpleName, "Get users $id rents") + val existRents = dbUserRepository.getUserRents(id).associateBy { it.id }.toMutableMap() + + service.getUserRents(id) + .map { it.toRent() } + .forEach { rent -> + Log.d(RestUserRepository::class.simpleName, "айди брони: ${rent.id}, и пользователя: ${rent.userId}") + if(rent.userId == id) { + val existRent = existRents[rent.id] + if (existRent == null) { + Log.d(RestUserRepository::class.simpleName, "бронирования нет в бд") + dbRentRepository.insertRent(rent) + } else if (existRent != rent) { + Log.d(RestUserRepository::class.simpleName, "бронирование есть в бд") + dbRentRepository.updateRent(rent) + } + existRents[rent.id] = rent + } + } + return existRents.map { it.value }.sortedBy { it.id } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/AppContainer.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/AppContainer.kt new file mode 100644 index 0000000..add2b14 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/AppContainer.kt @@ -0,0 +1,75 @@ +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, + ticketRestRepository, + AppDatabase.getInstance(context) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/AppDatabase.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/AppDatabase.kt new file mode 100644 index 0000000..81e6f7c --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/AppDatabase.kt @@ -0,0 +1,102 @@ +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 = "ticketservicedatabase" + + @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",50, 2539.4) + val flight2 = Flight(2, "Ульяновск", "Париж","18-12-2023 12:25", "19-12-2023 17:15",200, 8362.2) + val flight3 = Flight(3, "Ульяновск", "Сочи","24-12-2023 17:28", "24-12-2023 19:23", 100, 1934.5) + val flight4 = Flight(4, "Ульяновск", "Нижний Новгород","24-12-2023 08:10", "24-12-2023 10:00", 100, 1934.5) + val flight5 = Flight(5, "Ульяновск", "Самара","27-12-2023 13:00", "27-12-2023 14:00", 100, 1934.5) + val flight6 = Flight(6, "Ульяновск", "Крым","24-11-2023 17:25", "24-11-2023 19:50", 100, 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 } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/FlightDao.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/FlightDao.kt new file mode 100644 index 0000000..2a72edc --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/FlightDao.kt @@ -0,0 +1,37 @@ +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") + fun getAll(): List + + @Query("select * from flights where flights.id = :flightId") + fun getFlightById(flightId: Int): Flow + + @Query("select * from flights where flights.direction_from like :from or flights.direction_to like :to or flights.departure_date like :departureDate") + suspend fun findFlights(from: String, to: String, departureDate: String): List + + @Query("SELECT * FROM flights ORDER BY id ASC") + fun getFlights(): PagingSource + + @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() +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/RemoteKeysDao.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/RemoteKeysDao.kt new file mode 100644 index 0000000..3476e43 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/RemoteKeysDao.kt @@ -0,0 +1,20 @@ +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) + + @Query("DELETE FROM remote_keys WHERE type = :type") + suspend fun clearRemoteKeys(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/RentDao.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/RentDao.kt new file mode 100644 index 0000000..b916cbb --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/RentDao.kt @@ -0,0 +1,34 @@ +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 + + @Query("select * from rents where rents.id = :rentId") + fun getRentById(rentId: Int): Flow + + @Query("SELECT * FROM rents ORDER BY id ASC") + fun getRents(): PagingSource + + @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() +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/TicketDao.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/TicketDao.kt new file mode 100644 index 0000000..ee5ada3 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/TicketDao.kt @@ -0,0 +1,39 @@ +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 + + @Query("SELECT * FROM tickets ORDER BY id ASC") + fun getTickets(): PagingSource + + @Query("select * from tickets where tickets.id = :ticketId") + fun getTicketById(ticketId: Int): Flow + + @Query("select * from tickets where tickets.flight_id = :flightId") + fun getFlightsTickets(flightId: Int): List + + @Insert + suspend fun insert(vararg ticket: Ticket) + + @Update + suspend fun update(ticket: Ticket) + + @Delete + suspend fun delete(ticket: Ticket) + + @Query("DELETE FROM tickets") + suspend fun deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/UserDao.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/UserDao.kt new file mode 100644 index 0000000..5397377 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/dao/UserDao.kt @@ -0,0 +1,32 @@ +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 + + @Query("select * from users where users.id = :userId") + fun getUserById(userId: Int?): Flow + + @Query("select * from rents WHERE rents.user_id = :userId") + suspend fun getUserRents(userId: Int): List + + @Insert + suspend fun insert(user: User) + + @Update + suspend fun update(user: User) + + @Delete + suspend fun delete(user: User) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Flight.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Flight.kt new file mode 100644 index 0000000..2c3a57a --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Flight.kt @@ -0,0 +1,28 @@ +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, + var tickets_count: Int, + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/RemoteKeys.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/RemoteKeys.kt new file mode 100644 index 0000000..c20ed39 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/RemoteKeys.kt @@ -0,0 +1,27 @@ +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? +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Rent.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Rent.kt new file mode 100644 index 0000000..21c8e78 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Rent.kt @@ -0,0 +1,44 @@ +package ru.ulstu.`is`.airticketrentservice.database.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.PrimaryKey + +@Entity(tableName = "rents", + foreignKeys = [ + ForeignKey( + entity = User::class, + parentColumns = ["id"], + childColumns = ["user_id"], + onDelete = ForeignKey.RESTRICT, + onUpdate = ForeignKey.RESTRICT), + ForeignKey( + entity = Ticket::class, + parentColumns = ["id"], + childColumns = ["ticket_id"], + onDelete = ForeignKey.RESTRICT, + onUpdate = ForeignKey.RESTRICT) + ]) +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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/RoleEnum.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/RoleEnum.kt new file mode 100644 index 0000000..f715ca1 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/RoleEnum.kt @@ -0,0 +1,6 @@ +package ru.ulstu.`is`.airticketrentservice.database.models + +enum class RoleEnum { + Admin, + User +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Ticket.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Ticket.kt new file mode 100644 index 0000000..a0b4fd3 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/Ticket.kt @@ -0,0 +1,36 @@ +package ru.ulstu.`is`.airticketrentservice.database.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +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 + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/User.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/User.kt new file mode 100644 index 0000000..f7b1559 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/models/User.kt @@ -0,0 +1,34 @@ +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 + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/FlightRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/FlightRepository.kt new file mode 100644 index 0000000..91f673e --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/FlightRepository.kt @@ -0,0 +1,14 @@ +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 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 + fun getFlights(): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineFlightRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineFlightRepository.kt new file mode 100644 index 0000000..291c307 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineFlightRepository.kt @@ -0,0 +1,31 @@ +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 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> = Pager( + config = PagingConfig( + pageSize = 4, + enablePlaceholders = false + ), + pagingSourceFactory = flightDao::getFlights + ).flow + + fun getAllFlightsPagingSource(): PagingSource = flightDao.getFlights() + override suspend fun findFlights(from: String, to: String, departureDate: String): List = flightDao.findFlights(from, to, departureDate) + suspend fun clearFlights() = flightDao.deleteAll() + suspend fun insertFlights(flights: List) = + flightDao.insert(*flights.toTypedArray()) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineRemoteKeyRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineRemoteKeyRepository.kt new file mode 100644 index 0000000..2d92cec --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineRemoteKeyRepository.kt @@ -0,0 +1,16 @@ +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) = + remoteKeysDao.insertAll(remoteKeys) + + override suspend fun deleteRemoteKey(type: RemoteKeyType) = + remoteKeysDao.clearRemoteKeys(type) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineRentRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineRentRepository.kt new file mode 100644 index 0000000..457053f --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineRentRepository.kt @@ -0,0 +1,30 @@ +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 = rentDao.getAll() + override suspend fun getRentById(rentId: Int): Rent = rentDao.getRentById(rentId).first() + override fun getRents(): Flow> = Pager( + config = PagingConfig( + pageSize = 4, + enablePlaceholders = false + ), + pagingSourceFactory = rentDao::getRents + ).flow + fun getAllRentsPagingSource(): PagingSource = rentDao.getRents() + suspend fun clearRents() = rentDao.deleteAll() + suspend fun insertRents(rents: List) = + rentDao.insert(*rents.toTypedArray()) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineTicketRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineTicketRepository.kt new file mode 100644 index 0000000..d286180 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineTicketRepository.kt @@ -0,0 +1,31 @@ +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.Flight +import ru.ulstu.`is`.airticketrentservice.database.models.Ticket + +class OfflineTicketRepository(private val ticketDao: TicketDao) : TicketRepository { + override suspend fun insertTicket(ticket: Ticket) = ticketDao.insert(ticket) + override suspend fun updateTicket(ticket: Ticket) = ticketDao.update(ticket) + override suspend fun deleteTicket(ticket: Ticket) = ticketDao.delete(ticket) + override suspend fun getAllTickets(): List = ticketDao.getAll() + override suspend fun getTicketById(ticketId: Int): Ticket = ticketDao.getTicketById(ticketId).first() + override fun getTickets(): Flow> = Pager( + config = PagingConfig( + pageSize = 4, + enablePlaceholders = false + ), + pagingSourceFactory = ticketDao::getTickets + ).flow + fun getAllTicketsPagingSource(): PagingSource = ticketDao.getTickets() + override suspend fun getFlightsTickets(flightId: Int): List = ticketDao.getFlightsTickets(flightId) + suspend fun clearTickets() = ticketDao.deleteAll() + suspend fun insertTickets(tickets: List) = + ticketDao.insert(*tickets.toTypedArray()) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineUserRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineUserRepository.kt new file mode 100644 index 0000000..ed04a68 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/OfflineUserRepository.kt @@ -0,0 +1,15 @@ +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 = userDao.getAll() + override suspend fun getUserById(userId: Int): User = userDao.getUserById(userId).first() + override suspend fun getUserRents(id: Int): List = userDao.getUserRents(id) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/RemoteKeysRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/RemoteKeysRepository.kt new file mode 100644 index 0000000..3e3479e --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/RemoteKeysRepository.kt @@ -0,0 +1,10 @@ +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) + suspend fun deleteRemoteKey(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/RentRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/RentRepository.kt new file mode 100644 index 0000000..5969f77 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/RentRepository.kt @@ -0,0 +1,15 @@ +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 + suspend fun getRentById(rentId: Int): Rent + fun getRents(): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/TicketRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/TicketRepository.kt new file mode 100644 index 0000000..cdac5e4 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/TicketRepository.kt @@ -0,0 +1,15 @@ +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) + suspend fun updateTicket(ticket: Ticket) + suspend fun deleteTicket(ticket: Ticket) + suspend fun getAllTickets(): List + suspend fun getTicketById(ticketId: Int): Ticket + fun getTickets(): Flow> + suspend fun getFlightsTickets(flightId: Int): List +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/UserRepository.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/UserRepository.kt new file mode 100644 index 0000000..827b48b --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/database/repository/UserRepository.kt @@ -0,0 +1,13 @@ +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 + suspend fun getUserById(userId: Int): User + suspend fun getUserRents(id: Int): List +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/elements/EmailTextInput.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/elements/EmailTextInput.kt new file mode 100644 index 0000000..8daa7d4 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/elements/EmailTextInput.kt @@ -0,0 +1,59 @@ +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("Электронная почта") }, + 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)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/AuthNavGraph.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/AuthNavGraph.kt new file mode 100644 index 0000000..3a26e97 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/AuthNavGraph.kt @@ -0,0 +1,32 @@ +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(route = "login") + object Registration: AuthScreen(route="registration") +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/HomeNavGraph.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/HomeNavGraph.kt new file mode 100644 index 0000000..bed34f2 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/HomeNavGraph.kt @@ -0,0 +1,135 @@ +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.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.TicketEdit +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) + } + composable( + route = BottomBarScreen.RentList.route + ){ + RentList(navController, rentListViewModel, ticketListViewModel) + } + 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(navController) + } + composable( + route = BottomBarScreen.FlightEdit.route, + arguments = listOf( + navArgument("id") { + type = NavType.IntType + } + ) + ){ + FlightEdit(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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/RootNavGraph.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/RootNavGraph.kt new file mode 100644 index 0000000..b359a45 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/graphs/RootNavGraph.kt @@ -0,0 +1,59 @@ +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.TicketEditViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel + +const val USERID_ARGUMENT="userId" + +@Composable +fun RootNavigationGraph( + navController: NavHostController, + currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory), + registrationViewModel: RegistrationViewModel= viewModel(factory = AppViewModelProvider.Factory), + loginViewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory), + flightListViewModel: FlightListViewModel = viewModel(factory = AppViewModelProvider.Factory), + userListViewModel: UserListViewModel = 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, + 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/BottomBar.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/BottomBar.kt new file mode 100644 index 0000000..9bac344 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/BottomBar.kt @@ -0,0 +1,113 @@ +package ru.ulstu.`is`.airticketrentservice.navigation + +import android.content.res.Configuration +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.animation.AnimatedVisibility +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.fillMaxSize +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.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.Surface +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.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +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 androidx.navigation.compose.rememberNavController +import ru.ulstu.`is`.airticketrentservice.R +import ru.ulstu.`is`.airticketrentservice.ui.theme.AirTicketRentServiceTheme + +@Composable +fun BottomBar(navController: NavHostController){ + 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 -> + AddItem( + screen = screen, + currentDestination = currentDestination, + navController = navController + ) + } + } + } + } +} + +@Composable +fun RowScope.AddItem( + screen: BottomBarScreen, + currentDestination: NavDestination?, + navController: NavController +){ + val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true + + val background = if (selected) Color(0xFFFFFFFF) else Color.Transparent + + + 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), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = screen.icon, + contentDescription = "icon", + tint = Color.DarkGray + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/BottomBarScreen.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/BottomBarScreen.kt new file mode 100644 index 0000000..0bedaa5 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/BottomBarScreen.kt @@ -0,0 +1,110 @@ +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 +import ru.ulstu.`is`.airticketrentservice.R + +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 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 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 TicketEdit: BottomBarScreen( + route = "ticket-edit/{id}", + title ="", + icon = Icons.Filled.AccountCircle + ) { + fun passId(id:String): String{ + return "ticket-edit/$id" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/MainNavbar.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/MainNavbar.kt new file mode 100644 index 0000000..76ff5fa --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/MainNavbar.kt @@ -0,0 +1,362 @@ +package ru.ulstu.`is`.airticketrentservice.navigation + +import android.annotation.SuppressLint +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +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.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import androidx.navigation.navigation +import ru.ulstu.`is`.airticketrentservice.screen.RentList +import ru.ulstu.`is`.airticketrentservice.screen.UserEdit +import ru.ulstu.`is`.airticketrentservice.screen.FlightEdit +import ru.ulstu.`is`.airticketrentservice.R +import ru.ulstu.`is`.airticketrentservice.screen.Admin +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.LoadScreen +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.TicketEdit +import ru.ulstu.`is`.airticketrentservice.screen.UserList +import ru.ulstu.`is`.airticketrentservice.screen.auth.Login +import ru.ulstu.`is`.airticketrentservice.screen.auth.Registration +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.UserEditViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.UserListViewModel + + +//@OptIn(ExperimentalMaterial3Api::class) +//@Composable +//fun Topbar( +// navController: NavHostController, +// currentScreen: Screen? +//) { +// TopAppBar( +// colors = TopAppBarDefaults.topAppBarColors( +// containerColor = Color.Transparent, +// ), +// title = {}, +// navigationIcon = { +// if ( +// navController.previousBackStackEntry != null +// && (currentScreen == null || !currentScreen.showInBottomBar) +// ) { +// IconButton(onClick = { navController.navigateUp() }) { +// Icon( +// imageVector = Icons.Filled.ArrowBack, +// contentDescription = null, +// tint = colorResource(R.color.black) +// ) +// } +// } +// }, +// modifier = Modifier +// .padding(10.dp) +// .clip(RoundedCornerShape(20.dp)) +// ) +//} +// +//@Composable +//fun Navbar( +// navController: NavHostController, +// currentDestination: NavDestination? +//) { +// NavigationBar( +// Modifier +// .padding(10.dp) +// .clip(RoundedCornerShape(20.dp))) { +// Screen.bottomBarItems.forEach { screen -> +// NavigationBarItem( +// colors = NavigationBarItemDefaults.colors(colorResource(R.color.lightBlue)), +// icon = { Icon(screen.icon, contentDescription = null) }, +// label = { Text(stringResource(screen.resourceId)) }, +// selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, +// onClick = { +// navController.navigate(screen.route) { +// popUpTo(navController.graph.findStartDestination().id) { +// saveState = true +// } +// launchSingleTop = true +// restoreState = true +// } +// } +// ) +// } +// } +//} +// +//const val USERID_ARGUMENT="userId" +//@Composable +//fun RootNavGraph( +// navController: NavHostController, +// registrationViewModel: RegistrationViewModel= viewModel(factory = AppViewModelProvider.Factory), +// loginViewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory), +// currentUserViewModel: CurrentUserViewModel = 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( +// navController = navController, +// currentUserViewModel = currentUserViewModel +// ) +// } +// } +//} +// +//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(route = "login") +// object Registration : AuthScreen(route="registration") +//} +// +//@Composable +//fun HomeNavGraph( +// navController: NavHostController, +// currentUserViewModel: CurrentUserViewModel +//) { +// NavHost( +// navController = navController, +// startDestination = Screen.MainPage.route +// ) { +// composable(Screen.MainPage.route) { MainPage(navController) } +// composable(Screen.RentList.route) { RentList(navController) } +// composable(Screen.FlightList.route) { FlightList(navController) } +// composable(Screen.Profile.route) { Profile(navController, currentUserViewModel) } +// composable(Screen.UserList.route) { UserList(navController) } +//// composable(Screen.Login.route) { Login(navController) } +// composable(Screen.MyRents.route) { MyRents(navController) } +//// composable(Screen.Registration.route) { Registration(navController) } +// composable(Screen.Admin.route) { Admin(navController) } +// composable( +// Screen.FlightInfo.route, +// arguments = listOf(navArgument("id") { type = NavType.IntType }) +// ) { +// FlightInfo(navController) +// } +// composable( +// Screen.FoundFlights.route, +// arguments = listOf( +// navArgument("from") { type = NavType.StringType }, +// navArgument("to") { type = NavType.StringType }, +// navArgument("departureDate") { type = NavType.StringType } +// ) +// ) { +// FoundFlights(navController) +// } +// composable( +// Screen.UserEdit.route, +// arguments = listOf(navArgument("id") { type = NavType.IntType }) +// ) { +// UserEdit(navController) +// } +// composable( +// Screen.FlightEdit.route, +// arguments = listOf(navArgument("id") { type = NavType.IntType }) +// ) { +// FlightEdit(navController) +// } +// composable( +// Screen.TicketEdit.route, +// arguments = listOf(navArgument("id") { type = NavType.IntType }) +// ) { +// TicketEdit(navController) +// } +//// composable( +//// Screen.MyRents.route, +//// arguments = listOf(navArgument("id") { type = NavType.IntType }) +//// ) { +//// MyRents(navController) +//// } +// composable( +// Screen.RentEdit.route, +// arguments = listOf(navArgument("id") { type = NavType.IntType }) +// ) { +// RentEdit(navController) +// } +// } +//} +// +//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" +// } +//} +// +////@RequiresApi(Build.VERSION_CODES.O) +////@Composable +////fun Navhost( +//// navController: NavHostController, +//// innerPadding: PaddingValues, modifier: +//// Modifier = Modifier, +//// currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory), +//// loginViewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory), +//// registrationViewModel: RegistrationViewModel = viewModel(factory = AppViewModelProvider.Factory), +////) { +//// NavHost( +//// navController, +//// startDestination = Graph.AUTHENTICATION, +//// modifier.padding(innerPadding), +//// route = Graph.ROOT +//// ) { +//// authNavGraph(navController=navController,registrationViewModel,loginViewModel,currentUserViewModel) +//// composable(route=Graph.MAIN, +//// arguments = listOf(navArgument(USERID_ARGUMENT){ +//// type= NavType.StringType +//// }) +////// composable(Screen.RentList.route) { RentList(navController) } +////// composable(Screen.FlightList.route) { FlightList(navController) } +////// composable(Screen.Profile.route) { Profile(navController) } +////// composable(Screen.UserList.route) { UserList(navController) } +////// composable(Screen.Login.route) { Login(navController) } +////// composable(Screen.MyRents.route) { MyRents(navController) } +////// composable(Screen.Registration.route) { Registration(navController) } +////// composable(Screen.MainPage.route) { MainPage(navController) } +////// composable(Screen.Admin.route) { Admin(navController) } +////// composable( +////// Screen.TicketEdit.route, +////// arguments = listOf(navArgument("id") { type = NavType.IntType }) +////// ) { +////// TicketEdit(navController) +////// } +////// composable( +////// Screen.FlightInfo.route, +////// arguments = listOf(navArgument("id") { type = NavType.IntType }) +////// ) { +////// FlightInfo(navController) +////// } +////// composable( +////// Screen.FoundFlights.route, +////// arguments = listOf( +////// navArgument("from") { type = NavType.StringType }, +////// navArgument("to") { type = NavType.StringType }, +////// navArgument("departureDate") { type = NavType.StringType } +////// ) +////// ) { +////// FoundFlights(navController) +////// } +////// composable( +////// Screen.UserEdit.route, +////// arguments = listOf(navArgument("id") { type = NavType.IntType }) +////// ) { +////// UserEdit(navController) +////// } +////// composable( +////// Screen.FlightEdit.route, +////// arguments = listOf(navArgument("id") { type = NavType.IntType }) +////// ) { +////// FlightEdit(navController) +////// } +//////// composable( +//////// Screen.MyRents.route, +//////// arguments = listOf(navArgument("id") { type = NavType.IntType }) +//////// ) { +//////// MyRents(navController) +//////// } +////// composable( +////// Screen.RentEdit.route, +////// arguments = listOf(navArgument("id") { type = NavType.IntType }) +////// ) { +////// RentEdit(navController) +////// } +//// } +////} +// +//@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +//@RequiresApi(Build.VERSION_CODES.O) +//@Composable +//fun MainNavbar() { +// val navController = rememberNavController() +// val navBackStackEntry by navController.currentBackStackEntryAsState() +// val currentDestination = navBackStackEntry?.destination +// val currentScreen = currentDestination?.route?.let { Screen.getItem(it) } +// val registrationViewModel: RegistrationViewModel= viewModel(factory = AppViewModelProvider.Factory) +// val loginViewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory) +// val currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory) +// +// Scaffold( +// topBar = { +// Topbar(navController, currentScreen) +// }, +// bottomBar = { +// if (currentScreen == null || currentScreen.showInBottomBar) { +// Navbar(navController, currentDestination) +// } +// } +// ) { +// RootNavGraph(navController, registrationViewModel, loginViewModel, currentUserViewModel) +// } +//} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/Screen.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/Screen.kt new file mode 100644 index 0000000..34c6bef --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/Screen.kt @@ -0,0 +1,76 @@ +package ru.ulstu.`is`.airticketrentservice.navigation + +import androidx.annotation.StringRes +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.Favorite +import androidx.compose.material.icons.filled.Search +import androidx.compose.ui.graphics.vector.ImageVector +import ru.ulstu.`is`.airticketrentservice.R + +//enum class Screen( +// val route: String, +// @StringRes val resourceId: Int, +// val icon: ImageVector = Icons.Filled.Favorite, +// val showInBottomBar: Boolean = true +//) { +// Admin( +// "admin", R.string.rent_view_title, Icons.Filled.AdminPanelSettings +// ), +// FlightEdit( +// "flight-edit/{id}", R.string.ticket_view_title, showInBottomBar = false +// ), +// FlightInfo( +// "flight-info/{id}", R.string.ticket_view_title, showInBottomBar = false +// ), +// FlightList( +// "flight-list", R.string.ticket_main_title, showInBottomBar = false +// ), +// FoundFlights( +// "found-flights/{from}-{to}-{departureDate}", R.string.ticket_main_title, showInBottomBar = false +// ), +// MainPage( +// "main", R.string.rent_view_title, Icons.Filled.Search +// ), +// MyRents( +// "my-rents", R.string.ticket_view_title, showInBottomBar = false +// ), +// Profile( +// "profile", R.string.rent_view_title, Icons.Filled.AccountCircle +// ), +// RentEdit( +// "rent-edit/{id}", R.string.ticket_view_title, showInBottomBar = false +// ), +// RentList( +// "rent-list", R.string.ticket_main_title, showInBottomBar = false +// ), +// TicketEdit( +// "ticket-edit/{id}", R.string.ticket_view_title, showInBottomBar = false +// ), +// UserEdit( +// "user-edit/{id}", R.string.ticket_view_title, showInBottomBar = false +// ), +// UserList( +// "user-list", R.string.ticket_main_title, showInBottomBar = false +// ), +// Login( +// "login", R.string.rent_view_title, showInBottomBar = false +// ), +// Registration( +// "registration", R.string.rent_view_title, showInBottomBar = false +// ); +// +// companion object { +// val bottomBarItems = listOf( +// MainPage, +// Profile, +// Admin +// ) +// +// fun getItem(route: String): Screen? { +// val findRoute = route.split("/").first() +// return values().find { value -> value.route.startsWith(findRoute) } +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/TopBar.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/TopBar.kt new file mode 100644 index 0000000..b03cf03 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/navigation/TopBar.kt @@ -0,0 +1,49 @@ +package ru.ulstu.`is`.airticketrentservice.navigation + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +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.unit.dp +import androidx.navigation.NavHostController +import ru.ulstu.`is`.airticketrentservice.R + +//@OptIn(ExperimentalMaterial3Api::class) +//@Composable +//fun TopBar(navController: NavHostController, +// currentScreen: BottomBarScreen?) { +// TopAppBar( +// colors = TopAppBarDefaults.topAppBarColors( +// containerColor = Color.Transparent, +// ), +// title = {}, +// actions = {}, +// navigationIcon = { +// if ( +// navController.previousBackStackEntry != null +// && (currentScreen == null) +// ) { +// IconButton(onClick = { navController.navigateUp() }) { +// Icon( +// imageVector = Icons.Filled.ArrowBack, +// contentDescription = null, +// tint = colorResource(R.color.black) +// ) +// } +// } +// }, +// modifier = Modifier +// .padding(10.dp) +// .clip(RoundedCornerShape(20.dp)) +// ) +//} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/Admin.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/Admin.kt new file mode 100644 index 0000000..a0dfeda --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/Admin.kt @@ -0,0 +1,83 @@ +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.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.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("Пользователи") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightEdit.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightEdit.kt new file mode 100644 index 0000000..9ca9391 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightEdit.kt @@ -0,0 +1,253 @@ +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.tickets_count.toString(), + onValueChange = { onUpdate(flightUiState.flightDetails.copy(tickets_count = it.toInt())) }, + 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) + ) + + 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)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightInfo.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightInfo.kt new file mode 100644 index 0000000..d63788f --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightInfo.kt @@ -0,0 +1,148 @@ +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.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.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.R +import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen +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.viewModel.TicketEditViewModel +import java.util.Calendar +import java.util.Date + +@Composable +fun FlightInfo( + navController: NavController, + flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory) + ) { + FlightInfo( + flightUiState = flightViewModel.flightUiState, + onClick = { + val route = BottomBarScreen.TicketEdit.passId(0.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 + ) + 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 + ) + + Text(text = " ${flightUiState.flightDetails.departure_date}", fontSize = 15.sp, color = Color.DarkGray) + + Text(text = flightUiState.flightDetails.arrival_date, fontSize = 15.sp, color = Color.DarkGray) + + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = flightUiState.flightDetails.tickets_count.toString(), + onValueChange = { }, + 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 = 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 + ) + 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 = "Сформировать билет") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightList.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightList.kt new file mode 100644 index 0000000..3cc3f08 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FlightList.kt @@ -0,0 +1,243 @@ +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, + 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) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + Text( + text = String.format("%s %s", flight.direction_from, flight.direction_to) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FoundFlights.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FoundFlights.kt new file mode 100644 index 0000000..45cffe8 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/FoundFlights.kt @@ -0,0 +1,109 @@ +package ru.ulstu.`is`.airticketrentservice.screen + +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.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.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.getValue +import androidx.compose.ui.Alignment +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.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 +import ru.ulstu.`is`.airticketrentservice.viewModel.FoundFlightsUiState + +@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 -> + val route = BottomBarScreen.FlightInfo.passId(uid.toString()) + navController.navigate(route) + } + ) + } +} + +@Composable +private fun FoundFlights( + modifier: Modifier = Modifier, + flightList: List, + 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(), + 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}") + } + } +} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/MainPage.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/MainPage.kt new file mode 100644 index 0000000..9c3b455 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/MainPage.kt @@ -0,0 +1,245 @@ +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("") } + val to = remember { mutableStateOf("") } + val departureDate = remember { mutableStateOf("") } + + + 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) }, + ) { + Modifier + .padding(it) + HomeNavGraph( + navController = navController, + currentUserViewModel = currentUserViewModel, + flightListViewModel = flightListViewModel, + userListViewModel = userListViewModel, + ticketListViewModel = ticketListViewModel, + rentListViewModel = rentListViewModel + ) + } +} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/MyRents.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/MyRents.kt new file mode 100644 index 0000000..1984af8 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/MyRents.kt @@ -0,0 +1,219 @@ +package ru.ulstu.`is`.airticketrentservice.screen + +import android.util.Log +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.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +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.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.collectAsState +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.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +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 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.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.viewModel.RentListViewModel +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.UsersRentsViewModel + +@Composable +fun MyRents( + navController: NavController, + viewModel: UsersRentsViewModel = viewModel(factory = AppViewModelProvider.Factory), + currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + + val getUser by remember { mutableStateOf(currentUserViewModel.user) } + val 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) + } + } + ) + } +} + +@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: (id: 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) + .clip(shape = RoundedCornerShape(15.dp)) + .clickable { onClick(rent.id) }) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@Composable +private fun MyRents( + modifier: Modifier = Modifier, + rentList: List, + 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, + rent = rent, + onClick = onClick + ) + } + } + } + } +} + +@Composable +private fun RentListItem( + modifier: Modifier = Modifier, rent: Rent +) { + Card( + modifier = modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + Text( + text = "Бронирование ${rent.id}" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/Profile.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/Profile.kt new file mode 100644 index 0000000..0b518ae --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/Profile.kt @@ -0,0 +1,189 @@ +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.navigation.BottomBarScreen +import ru.ulstu.`is`.airticketrentservice.viewModel.toUiState + +@Composable +fun Profile( + navController: NavController, + currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val getUser by remember { mutableStateOf(currentUserViewModel.user) } + val userUiState = getUser?.toUiState(true) + Log.d("CurrentUserViewModel1", "Текущий пользователь: $getUser") + 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 = "${getUser?.surname} ${getUser?.name} ${getUser?.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 = "${getUser?.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 = "${getUser?.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(getUser?.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(getUser?.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("Мои бронирования") +// } +// ) +// Button( +// modifier = Modifier +// .fillMaxWidth() +// .padding(5.dp), +// onClick = { +// navController.navigate(AuthScreen.Login.route) +// }, +// elevation = ButtonDefaults.buttonElevation( +// defaultElevation = 10.dp, +// pressedElevation = 6.dp +// ), +// shape = RoundedCornerShape(15.dp), +// colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.lightBlue)), +// content = { +// Text("Выйти") +// } +// ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/RentEdit.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/RentEdit.kt new file mode 100644 index 0000000..381826f --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/RentEdit.kt @@ -0,0 +1,248 @@ +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( + 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) + RentEdit( + 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 RentEdit( + rentUiState: RentUiState, + userUiState: UserUiState, + ticketUiState: TicketUiState, + onClick: () -> Unit, + onUpdate: (RentDetails) -> Unit +) { + Column( + Modifier + .fillMaxWidth() + .padding(all = 10.dp) + ) { + TextField( + value = "${userUiState.userDetails.surname} ${userUiState.userDetails.name} ${userUiState.userDetails.patronymic}", + onValueChange = {}, + readOnly = true, + modifier = Modifier.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("Пользователь") + } + ) + + TextField( + value = "${ticketUiState.ticketDetails.ticket_cost}", + onValueChange = {}, + readOnly = true, + modifier = Modifier.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("Стоимость билета") + } + ) + + TextField( + value = "${ticketUiState.ticketDetails.passengers_count}", + onValueChange = {}, + readOnly = true, + modifier = Modifier.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("Количество пассажиров") + } + ) + + TextField( + value = "Ожидает подтверждения", + onValueChange = { onUpdate(rentUiState.rentDetails.copy(status = it)) }, + readOnly = true, + modifier = Modifier.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)) + } + ) + +// 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 = { +// 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)) + } + ) + + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/RentList.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/RentList.kt new file mode 100644 index 0000000..6341908 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/RentList.kt @@ -0,0 +1,255 @@ +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.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.database.models.Rent +import ru.ulstu.`is`.airticketrentservice.navigation.BottomBarScreen +import ru.ulstu.`is`.airticketrentservice.viewModel.AppViewModelProvider +import ru.ulstu.`is`.airticketrentservice.viewModel.RentListViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketListViewModel + +@Composable +fun RentList( + navController: NavController, + viewModel: RentListViewModel = viewModel(factory = AppViewModelProvider.Factory), + ticketListViewModel: TicketListViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val rentListUiState = viewModel.rentListUiState.collectAsLazyPagingItems() + + Scaffold( + topBar = {}, + floatingActionButton = { + FloatingActionButton( + onClick = { + val route = BottomBarScreen.RentEdit.passId(0.toString()) + navController.navigate(route) + }, + ) { + Icon(Icons.Filled.Add, "Добавить") + } + } + ) { innerPadding -> + RentList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + rentList = rentListUiState, + onClick = { uid: Int -> + val route = BottomBarScreen.RentEdit.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, + 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) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + Text( + text = "Бронирование ${rent.id}" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketEdit.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketEdit.kt new file mode 100644 index 0000000..a8a9581 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/TicketEdit.kt @@ -0,0 +1,198 @@ +package ru.ulstu.`is`.airticketrentservice.screen + +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.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.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.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.RentEditViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketDetails +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketEditViewModel +import ru.ulstu.`is`.airticketrentservice.viewModel.TicketUiState + +@Composable +fun TicketEdit( + navController: NavController, + viewModel: TicketEditViewModel = viewModel(factory = AppViewModelProvider.Factory), + flightViewModel: FlightEditViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + TicketEdit( + ticketUiState = viewModel.ticketUiState, + flightUiState = flightViewModel.flightUiState, + onClick = { + coroutineScope.launch { + viewModel.saveTicket() + navController.navigate(BottomBarScreen.RentEdit.passId(0.toString())) + } + }, + onUpdate = viewModel::updateUiState, + totalCost = flightViewModel.flightUiState.flightDetails.one_ticket_cost * viewModel.ticketUiState.ticketDetails.passengers_count.toDouble() + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TicketEdit( + ticketUiState: TicketUiState, + flightUiState: FlightUiState, + onClick: () -> Unit, + onUpdate: (TicketDetails) -> Unit, + totalCost: Double +) { + //ticketUiState.ticketDetails.ticket_cost = totalCost + 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 = totalCost.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 + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = ticketUiState.ticketDetails.passengers_count.toString(), + onValueChange = { onUpdate(ticketUiState.ticketDetails.copy(passengers_count = it.toInt())) }, + 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 = totalCost.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 = 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 = "Забронировать") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/UserEdit.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/UserEdit.kt new file mode 100644 index 0000000..9dbd430 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/UserEdit.kt @@ -0,0 +1,206 @@ +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() + currentUserViewModel.user = viewModel.userUiState.user + 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)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/UserList.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/UserList.kt new file mode 100644 index 0000000..2a4707d --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/UserList.kt @@ -0,0 +1,208 @@ +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.foundation.lazy.items +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.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.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 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, + user: User, + onClick: (id: Int) -> Unit +) { + SwipeToDismiss( + modifier = Modifier.zIndex(1f), + state = dismissState, + directions = setOf( + DismissDirection.EndToStart + ), + background = { + DismissBackground(dismissState) + }, + 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, + 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() } + ) + + if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) { + onSwipe(user) + } + + 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) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + Text( + text = String.format("%s %s %s", user.surname, user.name, user.patronymic?: "") + ) + } + } +} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/auth/Login.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/auth/Login.kt new file mode 100644 index 0000000..0e20790 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/auth/Login.kt @@ -0,0 +1,179 @@ +package ru.ulstu.`is`.airticketrentservice.screen.auth + +import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth +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.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.api.repository.RestUserRepository +import ru.ulstu.`is`.airticketrentservice.graphs.AuthScreen +import ru.ulstu.`is`.airticketrentservice.graphs.Graph + +@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>(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 coroutineScope = rememberCoroutineScope() + + 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("Пароль") }, + 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 + ), + shape = RoundedCornerShape(15.dp), + onClick = { + if (passwordValue.isNotEmpty() && isValidEmail(emailValue)) { + 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") + } + } + } + }, + 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) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/auth/Registration.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/auth/Registration.kt new file mode 100644 index 0000000..7edfc99 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/screen/auth/Registration.kt @@ -0,0 +1,343 @@ +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.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.OutlinedTextFieldDefaults +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.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.room.ColumnInfo +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.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>(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 = emailValue, onValueChange = { emailValue = it }, +// colors = TextFieldDefaults.textFieldColors( +// containerColor = Color.LightGray.copy(.2f), +// unfocusedIndicatorColor = Color.Transparent, +// focusedIndicatorColor = Color.Transparent +// ), +// shape = RoundedCornerShape(15.dp), +// label = { +// Text("Электронная почта") +// } +// ) + 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) + } + } + ) + Text( + text = isValid, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth(), + ) + 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, "user") + 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())) + } + } + } + } + } + } else { + isValid = "Данные не корректны" + } + }, + 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) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Color.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Color.kt new file mode 100644 index 0000000..7cde7d0 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Color.kt @@ -0,0 +1,11 @@ +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) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Theme.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Theme.kt new file mode 100644 index 0000000..78ca2fa --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Type.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Type.kt new file mode 100644 index 0000000..ca1718e --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/ui/theme/Type.kt @@ -0,0 +1,34 @@ +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 + ) + */ +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/AppViewModelProvider.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/AppViewModelProvider.kt new file mode 100644 index 0000000..d85512e --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/AppViewModelProvider.kt @@ -0,0 +1,74 @@ +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 { + 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 + ) + } + } +} + +fun CreationExtras.ticketApplication(): TicketApplication = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as TicketApplication) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/CurrentUserViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/CurrentUserViewModel.kt new file mode 100644 index 0000000..858c3af --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/CurrentUserViewModel.kt @@ -0,0 +1,29 @@ +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(null) + private val userid = mutableIntStateOf(0) + var user by mutableStateOf(null) + + fun setArgument(arg: String) { + argument.value = arg + userid.intValue = arg.toInt() + viewModelScope.launch { + user = userRepository.getUserById(userid.intValue) + } + } + + suspend fun updateUser(user: User) { + userRepository.updateUser(user) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FindFlightsViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FindFlightsViewModel.kt new file mode 100644 index 0000000..431a690 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FindFlightsViewModel.kt @@ -0,0 +1,36 @@ +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.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import ru.ulstu.`is`.airticketrentservice.database.AppContainer +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 { + foundFlightsUiState = FoundFlightsUiState(flightRepository.findFlights(from, to, departureDate)) + } + } +} + +data class FoundFlightsUiState(val flightList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FlightEditViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FlightEditViewModel.kt new file mode 100644 index 0000000..bb45bcb --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FlightEditViewModel.kt @@ -0,0 +1,104 @@ +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"]) + + init { + viewModelScope.launch { + if (flightUid > 0) { + flightUiState = flightRepository.getFlightById(flightUid) + .toUiState(true) + } + } + } + + fun updateUiState(flightDetails: FlightDetails) { + flightUiState = FlightUiState( + flightDetails = flightDetails, + isEntryValid = validateInput(flightDetails) + ) + } + + 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() + && tickets_count > 0 + && one_ticket_cost > 0 + } + } +} + +data class FlightUiState( + val flightDetails: FlightDetails = FlightDetails(), + val isEntryValid: Boolean = false +) + +data class FlightDetails( + val direction_from: String = "", + val direction_to: String = "", + val departure_date: String = "", + val arrival_date: String = "", + val tickets_count: Int = 0, + 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, + tickets_count = tickets_count, + 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, + tickets_count = tickets_count, + one_ticket_cost = one_ticket_cost +) + +fun Flight.toUiState(isEntryValid: Boolean = false): FlightUiState = FlightUiState( + flightDetails = this.toDetails(), + isEntryValid = isEntryValid +) + diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FlightListViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FlightListViewModel.kt new file mode 100644 index 0000000..222046a --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/FlightListViewModel.kt @@ -0,0 +1,40 @@ +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() { + + //val foundFlightsUiState: Flow> = flightRepository.findPagingFlights(from, to, departureDate) + var flightsListUiState: Flow> = flightRepository.getFlights() + +// val flightListUiState: StateFlow = flightRepository.getAllFlights().map { +// FlightListUiState(it) +// }.stateIn( +// scope = viewModelScope, +// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppContainer.TIMEOUT), +// initialValue = FlightListUiState() +// ) + + +// val _searchResults = MutableStateFlow>(PagingData.empty()) +// val searchResults: MutableStateFlow> = _searchResults +// +// fun searchFlights(from: String, to: String, departureDate: String) { +// viewModelScope.launch { +// val results = flightRepository.findPagingFlights(from, to, departureDate).single() +// _searchResults.value = results +// } +// } + + fun deleteFlight(flight: Flight) = viewModelScope.launch { + flightRepository.deleteFlight(flight) + } +} diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/LoginViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/LoginViewModel.kt new file mode 100644 index 0000000..cb6e098 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/LoginViewModel.kt @@ -0,0 +1,20 @@ +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>(emptyList()) + fun setUserList() { + viewModelScope.launch { + userList=userRepository.getAllUsers() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RegistrationViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RegistrationViewModel.kt new file mode 100644 index 0000000..165ba0a --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RegistrationViewModel.kt @@ -0,0 +1,25 @@ +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>() + val users: LiveData> get() = _users + + fun setUserList() { + viewModelScope.launch { + _users.value = userRepository.getAllUsers() + } + } + + suspend fun insertUser(user: User) { + userRepository.insertUser(user) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RentEditViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RentEditViewModel.kt new file mode 100644 index 0000000..d06c5ed --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RentEditViewModel.kt @@ -0,0 +1,85 @@ +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 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 { + if (rentUid > 0) { + rentUiState = rentRepository.getRentById(rentUid) + .toUiState(true) + } + } + } + + 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()) + } + } + } + + 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 +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RentListViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RentListViewModel.kt new file mode 100644 index 0000000..130a3a8 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/RentListViewModel.kt @@ -0,0 +1,44 @@ +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> = rentRepository.getRents() + +// private var userId: Int = 0 +// fun setUserId(userId: Int) { +// this.userId = userId +// } +// var userRentsUiState by mutableStateOf(UserRentsUiState()) +// private set +// +// init { +// viewModelScope.launch { +// if (userId > 0) { +// userRentsUiState = UserRentsUiState(userRepository.getUserRents(userId)) +// } +// } +// } + + suspend fun deleteRent(rent: Rent) { + rentRepository.deleteRent(rent) + } +} + diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/TicketEditViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/TicketEditViewModel.kt new file mode 100644 index 0000000..a60dc4e --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/TicketEditViewModel.kt @@ -0,0 +1,108 @@ +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.launch +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 { + ticketsListUiState = TicketsListUiState(ticketRepository.getAllTickets()) + if (ticketUid > 0) { + ticketUiState = ticketRepository.getTicketById(ticketUid) + .toUiState(true) + } + } + } + + fun setCurrentTicket(ticketId: Int) { + val ticket: Ticket? = + ticketsListUiState.ticketList.firstOrNull { ticket -> ticket.id == ticketId } + ticket?.let { updateTicketUiState(it) } + } + + fun updateTicketUiState(ticket: Ticket) { + ticketUiState = TicketUiState( + ticket = ticket + ) + } + + fun updateUiState(ticketDetails: TicketDetails) { + ticketUiState = TicketUiState( + ticketDetails = ticketDetails, + isEntryValid = validateInput(ticketDetails) + ) + } + + 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 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 = listOf()) diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/TicketListViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/TicketListViewModel.kt new file mode 100644 index 0000000..1da8709 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/TicketListViewModel.kt @@ -0,0 +1,34 @@ +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> = ticketRepository.getTickets() + +// private var userId: Int = 0 +// fun setUserId(userId: Int) { +// this.userId = userId +// } +// var userRentsUiState by mutableStateOf(UserRentsUiState()) +// private set +// +// init { +// viewModelScope.launch { +// if (userId > 0) { +// userRentsUiState = UserRentsUiState(userRepository.getUserRents(userId)) +// } +// } +// } + + suspend fun deleteTicket(ticket: Ticket) { + ticketRepository.deleteTicket(ticket) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UserEditViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UserEditViewModel.kt new file mode 100644 index 0000000..fa0ae9c --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UserEditViewModel.kt @@ -0,0 +1,126 @@ +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 { + usersListUiState = UsersListUiState(userRepository.getAllUsers()) + if (userUid > 0) { + userUiState = userRepository.getUserById(userUid) + .toUiState(true)!! + } + } + } + + fun setCurrentUser(userId: Int) { + val user: User? = + usersListUiState.userList.firstOrNull { user -> user.id == userId } + user?.let { updateUserUiState(it) } + } + + fun updateUserUiState(user: User) { + userUiState = UserUiState( + user = user + ) + } + + fun updateUiState(userDetails: UserDetails) { + userUiState = UserUiState( + userDetails = userDetails, + isEntryValid = validateInput(userDetails) + ) + } + + suspend fun saveUser() { + if (validateInput()) { + if (userUid > 0) { + userRepository.updateUser(userUiState.userDetails.toUser(userUid)) + } else { + userRepository.insertUser(userUiState.userDetails.toUser()) + } + } + } + + private fun validateInput(uiState: UserDetails = userUiState.userDetails): Boolean { + return with(uiState) { + 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 = listOf()) diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UserListViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UserListViewModel.kt new file mode 100644 index 0000000..1df9e37 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UserListViewModel.kt @@ -0,0 +1,33 @@ +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 = listOf()) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UsersRentsViewModel.kt b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UsersRentsViewModel.kt new file mode 100644 index 0000000..f0ba511 --- /dev/null +++ b/app/src/main/java/ru/ulstu/is/airticketrentservice/viewModel/UsersRentsViewModel.kt @@ -0,0 +1,38 @@ +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.launch +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 { + if (userUid > 0) { + userRentsUiState = UserRentsUiState(userRepository.getUserRents(userUid)) + } + } + } + + suspend fun deleteRent(rent: Rent) { + rentRepository.deleteRent(rent) + } +} + +data class UserRentsUiState(val rentList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000..7635bfb Binary files /dev/null and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3d6cee7 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,13 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #6AABC8 + #e7f0f5 + #fcfcff + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..f5bee6d --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,65 @@ + + AirTicketRentService + --> + Редактировать + Бронирование + Нет бронирования + Пользователь + Дата бронирования + Статус бронирования + Билет + Сохранить + Сохранить + Рейс не указан + Пользователь не указан + Записи о пользователях отсутствуют + Записи о билетах отсутствуют + Записи о бронированиях отсутствуют + Откуда + Куда + Дата вылета + Дата прилёта + Стоимость + ФИО + Дата рождения + Имя пользователя + Пароль + Место + Редактировать + Подтвердить + Вход + user@mail.ru + Password + Фамилия + Имя + Петров + Павел + Подтверждение пароля + Войти + Дата рождения + 2000-01-01 + Зарегистрироваться + Логин + Пароль + Билеты + Бронирования + Поиск авиабилетов + Регистрация + Выбрать даты + Фильтр + Поиск + Личный кабинет + Мои документы + Мои бронирования + Создать бронь + Написать в поддержку + "Главная" + "Профиль" + "Для администратора" + "" + "" + "" + "" + "" + "" + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..fe0620b --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +