Compare commits

...

22 Commits
4lab ... 5lab

Author SHA1 Message Date
Zyzf
f79fd6d658 Merge remote-tracking branch 'origin/5lab' into 5lab
# Conflicts:
#	app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt
2023-12-20 14:19:40 +04:00
Zyzf
362cf8e347 fixed reports 2023-12-20 14:18:37 +04:00
Zyzf
2f4910bf07 fixes 2023-12-20 14:18:34 +04:00
Zyzf
03d07bb9da fixed reports 2023-12-20 14:13:29 +04:00
Zyzf
e4b8fb9a5f fixes 2023-12-20 13:19:12 +04:00
zyzf
1819d11e39 edit fixes 2023-12-19 14:39:14 +04:00
zyzf
ca3c675988 5lab 2023-12-18 16:48:26 +04:00
zyzf
d3fde20a0a fixed warnings 2023-12-15 11:48:20 +04:00
zyzf
acd37136b0 fixed showing add and edit buttons for non admin users
fixed back button on exit from account
2023-12-15 11:30:37 +04:00
zyzf
042350ca59 fixed all bugs 2023-12-15 11:20:18 +04:00
Zyzf
cc6178a845 some night fixes 2023-12-15 01:10:34 +04:00
Zyzf
2a42e395cf some done 2023-12-14 22:16:22 +04:00
Zyzf
8c7ed46049 many done 2023-12-14 22:14:12 +04:00
zyzf
d2db976f34 some changes 2023-12-14 14:43:45 +04:00
Zyzf
ae769e167a fixed api 2023-12-14 00:59:56 +04:00
Zyzf
9360565c77 fixed imports 2023-12-13 13:46:34 +04:00
Zyzf
1ea8ed0e93 added user api 2023-12-13 13:46:05 +04:00
Zyzf
e4b1cf3daa pre done api for coffee 2023-12-12 21:05:54 +04:00
Zyzf
bb21aa8cac deleted cart fields for coffee 2023-12-12 20:03:24 +04:00
zyzf
fab50baea5 copied spring project 2023-12-12 16:09:47 +04:00
zyzf
176147f583 pre final for mobile app 2023-12-12 14:28:13 +04:00
zyzf
7c3af23e3b some beginning 2023-12-11 20:49:31 +04:00
90 changed files with 2296 additions and 470 deletions

42
.gitignore vendored
View File

@ -13,3 +13,45 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
data.mv.db
data.trace.db
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
node_modules

3
.idea/.gitignore vendored
View File

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

View File

@ -1 +0,0 @@
Coffee Preorder

View File

@ -3,7 +3,20 @@
<component name="deploymentTargetDropDown"> <component name="deploymentTargetDropDown">
<value> <value>
<entry key="app"> <entry key="app">
<State /> <State>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_7_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-12-20T08:37:35.519292927Z" />
</State>
</entry> </entry>
</value> </value>
</component> </component>

View File

@ -5,7 +5,7 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp") id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
} }
android { android {
@ -41,7 +42,7 @@ android {
compose = true compose = true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = "1.5.5" kotlinCompilerExtensionVersion = "1.5.3"
} }
packaging { packaging {
resources { resources {
@ -56,10 +57,11 @@ kotlin {
dependencies { dependencies {
// Retrofit // Retrofit
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("io.coil-kt:coil-compose:2.5.0") implementation("io.coil-kt:coil-compose:2.5.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("com.jcraft:jsch:0.1.55") implementation("com.jcraft:jsch:0.1.55")
@ -68,30 +70,30 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
// UI // UI
implementation("androidx.activity:activity-compose:1.8.1") implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2023.10.01")) implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.navigation:navigation-compose:2.7.5") implementation("androidx.navigation:navigation-compose:2.7.6")
implementation("androidx.compose.ui:ui:1.6.0-beta02") implementation("androidx.compose.ui:ui:1.6.0-beta03")
implementation("androidx.compose.ui:ui-graphics:1.6.0-beta02") implementation("androidx.compose.ui:ui-graphics:1.6.0-beta03")
implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta02") implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta03")
implementation("androidx.compose.material3:material3:1.1.2") implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.compose.material:material:1.5.4")
implementation("androidx.paging:paging-compose:3.2.1") implementation("androidx.paging:paging-compose:3.2.1")
implementation("eu.bambooapps:compose-material3-pullrefresh:1.0.0") implementation("eu.bambooapps:compose-material3-pullrefresh:1.0.1")
// Room // Room
val roomVersion = "2.6.1" implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-runtime:$roomVersion") annotationProcessor("androidx.room:room-compiler:2.6.1")
annotationProcessor("androidx.room:room-compiler:$roomVersion") ksp("androidx.room:room-compiler:2.6.1")
ksp("androidx.room:room-compiler:$roomVersion") implementation("androidx.room:room-ktx:2.6.1")
implementation("androidx.room:room-ktx:$roomVersion") implementation("androidx.room:room-paging:2.6.1")
implementation("androidx.room:room-paging:$roomVersion")
// Tests // Tests
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01")) androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.0-beta02") androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.0-beta03")
debugImplementation("androidx.compose.ui:ui-tooling:1.6.0-beta02") debugImplementation("androidx.compose.ui:ui-tooling:1.6.0-beta03")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0-beta02") debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0-beta03")
} }

View File

@ -1,21 +1,45 @@
# Add project specific ProGuard rules here. # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# You can control the set of applied configuration files using the # EnclosingMethod is required to use InnerClasses.
# proguardFiles setting in build.gradle. -keepattributes Signature, InnerClasses, EnclosingMethod
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following # Retrofit does reflection on method and parameter annotations.
# and specify the fully qualified class name to the JavaScript interface -keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for # Keep annotation default values (e.g., retrofit2.http.Field.encoded).
# debugging stack traces. -keepattributes AnnotationDefault
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to # Retain service method parameters when optimizing.
# hide the original source file name. -keepclassmembers,allowshrinking,allowobfuscation interface * {
#-renamesourcefileattribute SourceFile @retrofit2.http.* <methods>;
}
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response

View File

@ -16,7 +16,8 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.CoffeePreorder" android:theme="@style/Theme.CoffeePreorder"
tools:targetApi="31"> tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity <activity
android:name=".MainComposeActivity" android:name=".MainComposeActivity"
android:exported="true" android:exported="true"

View File

@ -3,10 +3,15 @@ package com.zyzf.coffeepreorder
import android.app.Application import android.app.Application
import com.zyzf.coffeepreorder.database.AppContainer import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDataContainer import com.zyzf.coffeepreorder.database.AppDataContainer
import com.zyzf.coffeepreorder.database.model.User
class CoffeeApplication : Application() { class CoffeeApplication : Application() {
lateinit var container: AppContainer lateinit var container: AppContainer
companion object {
var currentUser: User? = null
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
container = AppDataContainer(this) container = AppDataContainer(this)

View File

@ -1,15 +1,12 @@
package com.zyzf.coffeepreorder package com.zyzf.coffeepreorder
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.zyzf.coffeepreorder.ui.navigation.MainNavbar import com.zyzf.coffeepreorder.ui.navigation.MainNavbar
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme

View File

@ -0,0 +1,103 @@
package com.zyzf.coffeepreorder.api
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.zyzf.coffeepreorder.api.model.CoffeeRemote
import com.zyzf.coffeepreorder.api.model.UserRemote
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
interface MyServerService {
@GET("user/")
suspend fun getUsers(
@Query("pageNo") page: Int,
@Query("pageSize") limit: Int,
): List<UserRemote>
@GET("user/{id}")
suspend fun getUser(
@Path("id") id: Int,
): UserRemote
@GET("user/tryLogin")
suspend fun tryLogin(
@Query("login") login: String,
@Query("password") password: String
): UserRemote
@POST("user/")
suspend fun createUser(
@Body user: UserRemote,
): UserRemote
@PUT("user/{id}")
suspend fun updateUser(
@Path("id") id: Int,
@Body user: UserRemote,
): UserRemote
@DELETE("user/{id}")
suspend fun deleteUser(
@Path("id") id: Int,
): UserRemote
@GET("coffee/")
suspend fun getCoffees(
@Query("pageNo") page: Int,
@Query("pageSize") limit: Int,
): List<CoffeeRemote>
@GET("coffee/{id}")
suspend fun getCoffee(
@Path("id") id: Int,
): CoffeeRemote
@POST("coffee/")
suspend fun createCoffee(
@Body coffee: CoffeeRemote,
): CoffeeRemote
@PUT("coffee/{id}")
suspend fun updateCoffee(
@Path("id") id: Int,
@Body coffee: CoffeeRemote,
): CoffeeRemote
@DELETE("coffee/{id}")
suspend fun deleteCoffee(
@Path("id") id: Int,
): CoffeeRemote
companion object {
private const val BASE_URL = "http://192.168.0.100:8080/api/"
@Volatile
private var INSTANCE: MyServerService? = null
fun getInstance(): MyServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(MyServerService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,106 @@
package com.zyzf.coffeepreorder.api.coffee
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toCoffee
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.RemoteKeyType
import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class CoffeeRemoteMediator(
private val service: MyServerService,
private val dbCoffeeRepository: OfflineCoffeeRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Coffee>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Coffee>
): 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 coffees = service.getCoffees(page, state.config.pageSize).map { it.toCoffee() }
val endOfPaginationReached = coffees.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.COFFEE)
dbCoffeeRepository.clearCoffees()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = coffees.map {
RemoteKeys(
entityId = it.uid,
type = RemoteKeyType.COFFEE,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbCoffeeRepository.insertCoffees(coffees)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Coffee>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { coffee ->
dbRemoteKeyRepository.getAllRemoteKeys(coffee.uid, RemoteKeyType.COFFEE)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Coffee>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { coffee ->
dbRemoteKeyRepository.getAllRemoteKeys(coffee.uid, RemoteKeyType.COFFEE)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Coffee>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { coffeeUid ->
dbRemoteKeyRepository.getAllRemoteKeys(coffeeUid, RemoteKeyType.COFFEE)
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.zyzf.coffeepreorder.api.coffee
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toCoffee
import com.zyzf.coffeepreorder.api.model.toCoffeeRemote
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestCoffeeRepository(
private val service: MyServerService,
private val dbCoffeeRepository: OfflineCoffeeRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : CoffeeRepository {
override fun getAllCoffees(): Flow<PagingData<Coffee>> {
Log.d(RestCoffeeRepository::class.simpleName, "Get coffees")
val pagingSourceFactory = { dbCoffeeRepository.getAllCoffeesPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = CoffeeRemoteMediator(
service,
dbCoffeeRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getByUid(uid: Int): Coffee =
service.getCoffee(uid).toCoffee()
override suspend fun insert(coffee: Coffee): Long {
return service.createCoffee(coffee.toCoffeeRemote()).toCoffee().uid.toLong()
}
override suspend fun update(coffee: Coffee): Int {
return service.updateCoffee(coffee.uid, coffee.toCoffeeRemote()).toCoffee().uid
}
override suspend fun delete(coffee: Coffee) {
service.deleteCoffee(coffee.uid).toCoffee()
}
}

View File

@ -0,0 +1,26 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.database.model.Coffee
import kotlinx.serialization.Serializable
@Serializable
data class CoffeeRemote(
val id: Int = 0,
val name: String = "",
val cost: Double = 0.0,
val ingredients: String = ""
)
fun CoffeeRemote.toCoffee(): Coffee = Coffee(
id,
name,
cost,
ingredients
)
fun Coffee.toCoffeeRemote(): CoffeeRemote = CoffeeRemote(
uid,
name,
cost,
ingredients
)

View File

@ -0,0 +1,32 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.database.model.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int = 0,
val login: String = "",
val fio: String = "",
val phone: String = "",
val password: String = "",
val role: String = ""
)
fun UserRemote.toUser(): User = User(
id,
login,
fio,
phone,
password,
role
)
fun User.toUserRemote(): UserRemote = UserRemote(
uid,
login,
fio,
phone,
password,
role
)

View File

@ -0,0 +1,64 @@
package com.zyzf.coffeepreorder.api.user
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toUser
import com.zyzf.coffeepreorder.api.model.toUserRemote
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository
import com.zyzf.coffeepreorder.database.repository.UserRepository
import kotlinx.coroutines.flow.Flow
class RestUserRepository(
private val service: MyServerService,
private val dbUserRepository: OfflineUserRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : UserRepository {
override fun getAll(): Flow<PagingData<User>> {
Log.d(RestUserRepository::class.simpleName, "Get users")
val pagingSourceFactory = { dbUserRepository.getAllUserPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = UserRemoteMediator(
service,
dbUserRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getByUid(uid: Int): User =
service.getUser(uid).toUser()!!
override suspend fun tryLogin(login: String, password: String): User? =
service.tryLogin(login, password).toUser()
override suspend fun insert(user: User): Long {
return service.createUser(user.toUserRemote()).toUser()?.uid?.toLong()!!
}
override suspend fun update(user: User): Int {
return service.updateUser(user.uid, user.toUserRemote()).toUser()?.uid!!
}
override suspend fun delete(user: User) {
service.deleteUser(user.uid).toUser()
}
}

View File

@ -0,0 +1,106 @@
package com.zyzf.coffeepreorder.api.user
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toUser
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.RemoteKeyType
import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class UserRemoteMediator(
private val service: MyServerService,
private val dbUserRepository: OfflineUserRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, User>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, User>
): 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 users = service.getUsers(page, state.config.pageSize).map { it.toUser() }
val endOfPaginationReached = users.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.USER)
dbUserRepository.clearUsers()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = users.map {
RemoteKeys(
entityId = it.uid,
type = RemoteKeyType.USER,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbUserRepository.insertUsers(users)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, User>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { user ->
dbRemoteKeyRepository.getAllRemoteKeys(user.uid, RemoteKeyType.USER)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, User>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { user ->
dbRemoteKeyRepository.getAllRemoteKeys(user.uid, RemoteKeyType.USER)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, User>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { userUid ->
dbRemoteKeyRepository.getAllRemoteKeys(userUid, RemoteKeyType.USER)
}
}
}
}

View File

@ -1,32 +1,52 @@
package com.zyzf.coffeepreorder.database package com.zyzf.coffeepreorder.database
import android.content.Context import android.content.Context
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.coffee.RestCoffeeRepository
import com.zyzf.coffeepreorder.api.user.RestUserRepository
import com.zyzf.coffeepreorder.database.repository.CartRepository import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository
import com.zyzf.coffeepreorder.database.repository.UserRepository
interface AppContainer { interface AppContainer {
val coffeeRepository: CoffeeRepository
val userRepository: UserRepository
val cartRepository: CartRepository val cartRepository: CartRepository
val coffeeRestRepository: RestCoffeeRepository
val userRestRepository: RestUserRepository
companion object {
const val LIMIT = 10
}
} }
class AppDataContainer(private val context: Context) : AppContainer { class AppDataContainer(private val context: Context) : AppContainer {
override val coffeeRepository: CoffeeRepository by lazy { private val coffeeRepository: OfflineCoffeeRepository by lazy {
OfflineCoffeeRepository(AppDatabase.getInstance(context).coffeeDao()) OfflineCoffeeRepository(AppDatabase.getInstance(context).coffeeDao())
} }
override val userRepository: UserRepository by lazy { private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao()) OfflineUserRepository(AppDatabase.getInstance(context).userDao())
} }
override val cartRepository: CartRepository by lazy { override val cartRepository: CartRepository by lazy {
OfflineCartRepository(AppDatabase.getInstance(context).cartDao()) OfflineCartRepository(AppDatabase.getInstance(context).cartDao())
} }
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
companion object { OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
const val TIMEOUT = 5000L }
const val LIMIT = 10 override val coffeeRestRepository: RestCoffeeRepository by lazy {
RestCoffeeRepository(
MyServerService.getInstance(),
coffeeRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
MyServerService.getInstance(),
userRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
} }
} }

View File

@ -4,23 +4,21 @@ import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.zyzf.coffeepreorder.database.dao.CartDao import com.zyzf.coffeepreorder.database.dao.CartDao
import com.zyzf.coffeepreorder.database.dao.CoffeeDao import com.zyzf.coffeepreorder.database.dao.CoffeeDao
import com.zyzf.coffeepreorder.database.dao.RemoteKeysDao
import com.zyzf.coffeepreorder.database.dao.UserDao import com.zyzf.coffeepreorder.database.dao.UserDao
import com.zyzf.coffeepreorder.database.model.Cart import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.model.UserLogined
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Database(entities = [User::class, Coffee::class, Cart::class, UserLogined::class], version = 1, exportSchema = false) @Database(entities = [User::class, Coffee::class, Cart::class, RemoteKeys::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
abstract fun coffeeDao(): CoffeeDao abstract fun coffeeDao(): CoffeeDao
abstract fun cartDao(): CartDao abstract fun cartDao(): CartDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object { companion object {
private const val DB_NAME: String = "coffee-preorder" private const val DB_NAME: String = "coffee-preorder"
@ -36,31 +34,31 @@ abstract class AppDatabase : RoomDatabase() {
userDao.insert(user1) userDao.insert(user1)
// Coffees // Coffees
val coffeeDao = database.coffeeDao() val coffeeDao = database.coffeeDao()
val coffee1 = Coffee("Coffee1", 200.0, "Ing1", null, 0) val coffee1 = Coffee("Coffee1", 200.0, "Ing1")
val coffee2 = Coffee("Coffee2", 200.0, "Ing1", null, 0) val coffee2 = Coffee("Coffee2", 200.0, "Ing1")
val coffee3 = Coffee("Coffee3", 300.0, "Ing1", null, 0) val coffee3 = Coffee("Coffee3", 300.0, "Ing1")
val coffee4 = Coffee("Coffee4", 200.0, "Ing1", null, 0) val coffee4 = Coffee("Coffee4", 200.0, "Ing1")
val coffee5 = Coffee("Coffee5", 200.0, "Ing1", null, 0) val coffee5 = Coffee("Coffee5", 200.0, "Ing1")
val coffee6 = Coffee("Coffee6", 25.0, "Ing1", null, 0) val coffee6 = Coffee("Coffee6", 25.0, "Ing1")
val coffee7 = Coffee("Coffee7", 400.0, "Ing1", null, 0) val coffee7 = Coffee("Coffee7", 400.0, "Ing1")
val coffee8 = Coffee("Coffee8", 200.0, "Ing1", null, 0) val coffee8 = Coffee("Coffee8", 200.0, "Ing1")
val coffee9 = Coffee("Coffee9", 200.0, "Ing1", null, 0) val coffee9 = Coffee("Coffee9", 200.0, "Ing1")
val coffee10 = Coffee("Coffee10", 900.0, "Ing1", null, 0) val coffee10 = Coffee("Coffee10", 900.0, "Ing1")
val coffee11 = Coffee("Coffee11", 200.0, "Ing1", null, 0) val coffee11 = Coffee("Coffee11", 200.0, "Ing1")
coffeeDao.insert(coffee1.name, coffee1.cost, coffee1.ingredients) coffeeDao.insert(coffee1)
coffeeDao.insert(coffee2.name, coffee2.cost, coffee2.ingredients) coffeeDao.insert(coffee2)
coffeeDao.insert(coffee3.name, coffee3.cost, coffee3.ingredients) coffeeDao.insert(coffee3)
coffeeDao.insert(coffee4.name, coffee4.cost, coffee4.ingredients) coffeeDao.insert(coffee4)
coffeeDao.insert(coffee5.name, coffee5.cost, coffee5.ingredients) coffeeDao.insert(coffee5)
coffeeDao.insert(coffee6.name, coffee6.cost, coffee6.ingredients) coffeeDao.insert(coffee6)
coffeeDao.insert(coffee7.name, coffee7.cost, coffee7.ingredients) coffeeDao.insert(coffee7)
coffeeDao.insert(coffee8.name, coffee8.cost, coffee8.ingredients) coffeeDao.insert(coffee8)
coffeeDao.insert(coffee9.name, coffee9.cost, coffee9.ingredients) coffeeDao.insert(coffee9)
coffeeDao.insert(coffee10.name, coffee10.cost, coffee10.ingredients) coffeeDao.insert(coffee10)
coffeeDao.insert(coffee11.name, coffee11.cost, coffee11.ingredients) coffeeDao.insert(coffee11)
// Cart // Cart
val cartDao = database.cartDao() val cartDao = database.cartDao()
val cart = Cart() val cart = Cart(2, 1)
cartDao.insert(cart) cartDao.insert(cart)
} }
} }
@ -72,14 +70,15 @@ abstract class AppDatabase : RoomDatabase() {
AppDatabase::class.java, AppDatabase::class.java,
DB_NAME DB_NAME
) )
.addCallback(object : Callback() { // .addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) { // override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db) // super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch { // CoroutineScope(Dispatchers.IO).launch {
populateDatabase() // populateDatabase()
} // }
} // }
}) // })
.allowMainThreadQueries()
.build() .build()
.also { INSTANCE = it } .also { INSTANCE = it }
} }

View File

@ -1,23 +1,48 @@
package com.zyzf.coffeepreorder.database.dao package com.zyzf.coffeepreorder.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import com.zyzf.coffeepreorder.database.model.Cart import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee
@Dao @Dao
interface CartDao { interface CartDao {
@Query("select * from cart limit 1") @Query("select * from cart")
suspend fun get(): Cart suspend fun getAll(): Cart
@Query("select coffee.uid, coffee.name, coffee.cost, coffee.ingredients from cart join coffee on coffee.uid = cart.coffee_id and cart.count > 0 collate nocase")
fun getAllInCart(): PagingSource<Int, Coffee>
@Query("select sum(coffee.cost * cart.count) from cart JOIN coffee on coffee.uid = cart.coffee_id and cart.count > 0")
fun getSumInCart(): Double
@Query("select cart.count from cart JOIN coffee on coffee.uid = cart.coffee_id where coffee.uid = :coffeeId")
fun getCountForCoffee(coffeeId: Int): Double
@Insert @Insert
suspend fun insert(cart: Cart) suspend fun insert(cart: Cart)
@Query("update coffee set cart_id = :cartId, count = count + :count where uid = :coffeeId") @Query("select * from cart where coffee_id = :coffeeId limit 1")
suspend fun insertCoffee(cartId: Int, coffeeId: Int, count: Int) suspend fun findCoffee(coffeeId: Int): Cart?
@Query("insert into cart (coffee_id, count) values (:coffeeId, :count)")
suspend fun insertNewCoffee(coffeeId: Int, count: Int)
@Query("update cart set count = count + :count where coffee_id = :coffeeId")
suspend fun updateExistingCoffee(coffeeId: Int, count: Int)
@Transaction
suspend fun insertCoffee(coffeeId: Int, count: Int) {
val cart: Cart? = findCoffee(coffeeId)
if (cart != null) {
updateExistingCoffee(coffeeId, count)
} else {
insertNewCoffee(coffeeId, count)
}
}
@Query("update coffee set count = count - :count where uid = :coffeeId") @Query("update cart set count = count - :count where coffee_id = :coffeeId")
suspend fun deleteCoffee(coffeeId: Int, count: Int) suspend fun deleteCoffee(coffeeId: Int, count: Int)
@Update @Update

View File

@ -3,30 +3,31 @@ package com.zyzf.coffeepreorder.database.dao
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
@Dao @Dao
interface CoffeeDao { interface CoffeeDao {
@Query("select * from coffee order by name collate nocase asc") @Query("select * from coffee order by name collate nocase asc")
fun getAll(): PagingSource<Int, Coffee> fun getAllCoffees(): PagingSource<Int, Coffee>
@Query("select * from coffee where cart_id is not null and count > 0 order by name collate nocase asc") @Query("select coffee.uid, name, cost, ingredients from coffee where coffee.uid = :uid")
fun getAllInCart(): PagingSource<Int, Coffee> suspend fun getByUid(uid: Int): Coffee?
@Query("select sum(cost) from coffee where cart_id is not null and count > 0") @Insert
fun getSumInCart(): Double fun insert(coffee: Coffee): Long
@Query("select coffee.uid, name, cost, ingredients, cart_id, count, cart.uid as cart_uid from coffee left join cart on coffee.cart_id = cart.uid where coffee.uid = :uid") @Insert
suspend fun getByUid(uid: Int): CoffeeWithCart? suspend fun insert(vararg coffee: Coffee)
@Query("insert into coffee (name, cost, ingredients, count) values (:name, :cost, :ingredients, 0)") @Update
suspend fun insert(name: String, cost: Double, ingredients: String): Long fun update(coffee: Coffee): Int
@Query("update coffee set name = :name, cost = :cost, ingredients = :ingredients, cart_id = :cartId, count = :count where uid = :uid")
suspend fun update(uid: Int, name: String, cost: Double, ingredients: String, cartId: Int?, count: Int): Int?
@Delete @Delete
suspend fun delete(coffee: Coffee) suspend fun delete(coffee: Coffee)
@Query("delete from coffee")
suspend fun deleteAll()
} }

View File

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

View File

@ -1,15 +1,17 @@
package com.zyzf.coffeepreorder.database.dao package com.zyzf.coffeepreorder.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface UserDao { interface UserDao {
@Query("select * from user") @Query("select * from user")
fun getAll(): Flow<List<User>> fun getAll(): PagingSource<Int, User>
@Query("select * from user where login = :login and password = :password") @Query("select * from user where login = :login and password = :password")
suspend fun tryLogin(login: String, password: String): User? suspend fun tryLogin(login: String, password: String): User?
@ -17,21 +19,18 @@ interface UserDao {
@Query("select * from user where uid = :uid") @Query("select * from user where uid = :uid")
suspend fun getByUid(uid: Int): User? suspend fun getByUid(uid: Int): User?
@Query("select user.uid, login, fio, phone, password, role from user join user_logined on user_logined.user_id = user.uid limit 1") @Insert
suspend fun getLogined(): User? fun insert(user: User): Long
@Query("insert into user_logined (user_id) values (:userId)")
suspend fun setLogined(userId: Int)
@Query("delete from user_logined")
suspend fun logout()
@Insert @Insert
suspend fun insert(user: User) fun insert(vararg user: User)
@Query("update user set login = :login, fio = :fio, phone = :phone, password = :password where uid = :uid") @Update
suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int? fun update(user: User): Int?
@Query("delete from user where uid = :uid") @Delete
suspend fun delete(uid: Int) suspend fun delete(user: User)
@Query("delete from user")
suspend fun deleteAll()
} }

View File

@ -1,25 +1,47 @@
package com.zyzf.coffeepreorder.database.model package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "cart") @Entity(tableName = "cart", foreignKeys = [
ForeignKey(
entity = Cart::class,
parentColumns = ["uid"],
childColumns = ["coffee_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
])
data class Cart( data class Cart(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "coffee_id", index = true)
val coffeeId: Int,
@ColumnInfo(name = "count", index = true)
val count: Int = 0
) { ) {
@Ignore @Ignore
constructor() : this(null) constructor(
coffeeId: Int,
count: Int
) : this(0, coffeeId, count)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Cart other as Cart
return uid == other.uid if (uid != other.uid) return false
if (coffeeId != other.coffeeId) return false
return count == other.count
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid ?: -1 var result = uid
result = 31 * result + coffeeId.hashCode()
result = 31 * result + count.hashCode()
return result
} }
} }

View File

@ -2,19 +2,10 @@ package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "coffee", foreignKeys = [ @Entity(tableName = "coffee")
ForeignKey(
entity = Cart::class,
parentColumns = ["uid"],
childColumns = ["cart_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
])
data class Coffee( data class Coffee(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int = 0, val uid: Int = 0,
@ -23,20 +14,14 @@ data class Coffee(
@ColumnInfo(name = "cost") @ColumnInfo(name = "cost")
var cost: Double, var cost: Double,
@ColumnInfo(name = "ingredients") @ColumnInfo(name = "ingredients")
var ingredients: String, var ingredients: String
@ColumnInfo(name = "cart_id", index = true)
val cartId: Int?,
@ColumnInfo(name = "count")
val count: Int = 0
) { ) {
@Ignore @Ignore
constructor( constructor(
name: String, name: String,
cost: Double, cost: Double,
ingredients: String, ingredients: String
cartId: Int?, ) : this(0, name, cost, ingredients)
count: Int?
) : this(0, name, cost, ingredients, null, 0)
companion object { companion object {
fun getCoffee(index: Int = 0): Coffee { fun getCoffee(index: Int = 0): Coffee {
@ -44,9 +29,7 @@ data class Coffee(
index, index,
"", "",
0.0, 0.0,
"", ""
null,
0
) )
} }
} }
@ -55,10 +38,17 @@ data class Coffee(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Coffee other as Coffee
return uid == other.uid if (uid != other.uid) return false
if (name != other.name) return false
if (cost != other.cost) return false
return ingredients == other.ingredients
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid var result = uid
result = 31 * result + name.hashCode()
result = 31 * result + cost.hashCode()
result = 31 * result + ingredients.hashCode()
return result
} }
} }

View File

@ -1,11 +0,0 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
data class CoffeeWithCart(
@Embedded
val coffee: Coffee,
@ColumnInfo(name = "cart_uid")
val cartUid: Int
)

View File

@ -0,0 +1,27 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
COFFEE(Coffee::class.simpleName ?: "Coffee"),
USER(User::class.simpleName ?: "User");
@TypeConverter
fun toRemoteKeyType(value: String) = entries.first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -8,7 +8,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "user") @Entity(tableName = "user")
data class User( data class User(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "login") @ColumnInfo(name = "login")
var login: String, var login: String,
@ColumnInfo(name = "fio") @ColumnInfo(name = "fio")
@ -27,16 +27,40 @@ data class User(
phone: String, phone: String,
password: String, password: String,
role: String role: String
) : this(null, login, fio, phone, password, role) ) : this(0, login, fio, phone, password, role)
companion object {
fun getUser(index: Int = 0): User {
return User(
index,
"",
"",
"",
"",
"user"
)
}
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as User other as User
return uid == other.uid if (uid != other.uid) return false
if (login != other.login) return false
if (fio != other.fio) return false
if (phone != other.phone) return false
if (password != other.password) return false
return role == other.role
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid ?: -1 var result = uid
result = 31 * result + login.hashCode()
result = 31 * result + fio.hashCode()
result = 31 * result + phone.hashCode()
result = 31 * result + password.hashCode()
result = 31 * result + role.hashCode()
return result
} }
} }

View File

@ -1,39 +0,0 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(tableName = "user_logined", foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["uid"],
childColumns = ["user_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)]
)
data class UserLogined(
@PrimaryKey(autoGenerate = true)
val uid: Int?,
@ColumnInfo(name = "user_id", index = true)
val userId: Int?
) {
@Ignore
constructor(
userId: Int?
) : this(null, userId)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UserLogined
return uid == other.uid
}
override fun hashCode(): Int {
return uid ?: -1
}
}

View File

@ -1,12 +1,18 @@
package com.zyzf.coffeepreorder.database.repository package com.zyzf.coffeepreorder.database.repository
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Cart import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee
import kotlinx.coroutines.flow.Flow
interface CartRepository { interface CartRepository {
suspend fun get(): Cart suspend fun getAll(): Cart
suspend fun insert(cart: Cart) suspend fun insert(cart: Cart)
suspend fun insertCoffee(cartId: Int, coffeeId: Int, count: Int) fun getAllInCart(): Flow<PagingData<Coffee>>
fun getSumInCart(): Double
suspend fun insertCoffee(coffeeId: Int, count: Int)
suspend fun deleteCoffee(coffeeId: Int, count: Int) suspend fun deleteCoffee(coffeeId: Int, count: Int)
fun getCountForCoffee(coffeeId: Int): Double
suspend fun update(cart: Cart) suspend fun update(cart: Cart)
suspend fun deleteAll() suspend fun deleteAll()
} }

View File

@ -2,15 +2,12 @@ package com.zyzf.coffeepreorder.database.repository
import androidx.paging.PagingData import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface CoffeeRepository { interface CoffeeRepository {
fun getAll(): Flow<PagingData<Coffee>> fun getAllCoffees(): Flow<PagingData<Coffee>>
fun getAllInCart(): Flow<PagingData<Coffee>> suspend fun getByUid(uid: Int): Coffee?
fun getSumInCart(): Double suspend fun insert(coffee: Coffee): Long
suspend fun getByUid(uid: Int): CoffeeWithCart? suspend fun update(coffee: Coffee): Int
suspend fun insert(name: String, cost: Double, ingredients: String): Long
suspend fun update(uid: Int, name: String, cost: Double, ingredients: String, cartId: Int?, count: Int): Int?
suspend fun delete(coffee: Coffee) suspend fun delete(coffee: Coffee)
} }

View File

@ -1,13 +1,28 @@
package com.zyzf.coffeepreorder.database.repository package com.zyzf.coffeepreorder.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.dao.CartDao import com.zyzf.coffeepreorder.database.dao.CartDao
import com.zyzf.coffeepreorder.database.model.Cart import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee
import kotlinx.coroutines.flow.Flow
class OfflineCartRepository(private val cartDao: CartDao) : CartRepository { class OfflineCartRepository(private val cartDao: CartDao) : CartRepository {
override suspend fun get(): Cart = cartDao.get() override fun getAllInCart(): Flow<PagingData<Coffee>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = cartDao::getAllInCart
).flow
override fun getSumInCart(): Double = cartDao.getSumInCart()
override suspend fun getAll(): Cart = cartDao.getAll()
override suspend fun insert(cart: Cart) = cartDao.insert(cart) override suspend fun insert(cart: Cart) = cartDao.insert(cart)
override suspend fun insertCoffee(cartId: Int, coffeeId: Int, count: Int) = cartDao.insertCoffee(cartId, coffeeId, count) override suspend fun insertCoffee(coffeeId: Int, count: Int) = cartDao.insertCoffee(coffeeId, count)
override suspend fun deleteCoffee(coffeeId: Int, count: Int) = cartDao.deleteCoffee(coffeeId, count) override suspend fun deleteCoffee(coffeeId: Int, count: Int) = cartDao.deleteCoffee(coffeeId, count)
override fun getCountForCoffee(coffeeId: Int): Double = cartDao.getCountForCoffee(coffeeId)
override suspend fun update(cart: Cart) = cartDao.update(cart) override suspend fun update(cart: Cart) = cartDao.update(cart)
override suspend fun deleteAll() = cartDao.deleteAll() override suspend fun deleteAll() = cartDao.deleteAll()
} }

View File

@ -3,30 +3,27 @@ package com.zyzf.coffeepreorder.database.repository
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.AppDataContainer import androidx.paging.PagingSource
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.dao.CoffeeDao import com.zyzf.coffeepreorder.database.dao.CoffeeDao
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository { class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository {
override fun getAll(): Flow<PagingData<Coffee>> = Pager( override fun getAllCoffees(): Flow<PagingData<Coffee>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = AppDataContainer.LIMIT, pageSize = AppContainer.LIMIT,
enablePlaceholders = false enablePlaceholders = false
), ),
pagingSourceFactory = coffeeDao::getAll pagingSourceFactory = coffeeDao::getAllCoffees
).flow ).flow
override fun getAllInCart(): Flow<PagingData<Coffee>> = Pager(
config = PagingConfig( fun getAllCoffeesPagingSource(): PagingSource<Int, Coffee> = coffeeDao.getAllCoffees()
pageSize = AppDataContainer.LIMIT, suspend fun clearCoffees() = coffeeDao.deleteAll()
enablePlaceholders = false override suspend fun getByUid(uid: Int): Coffee? = coffeeDao.getByUid(uid)
), override suspend fun insert(coffee: Coffee): Long = coffeeDao.insert(coffee)
pagingSourceFactory = coffeeDao::getAllInCart suspend fun insertCoffees(coffees: List<Coffee>) =
).flow coffeeDao.insert(*coffees.toTypedArray())
override fun getSumInCart(): Double = coffeeDao.getSumInCart() override suspend fun update(coffee: Coffee): Int = coffeeDao.update(coffee)
override suspend fun getByUid(uid: Int): CoffeeWithCart? = coffeeDao.getByUid(uid)
override suspend fun insert(name: String, cost: Double, ingredients: String): Long = coffeeDao.insert(name, cost, ingredients)
override suspend fun update(uid: Int, name: String, cost: Double, ingredients: String, cartId: Int?, count: Int): Int? = coffeeDao.update(uid, name, cost, ingredients, cartId, count)
override suspend fun delete(coffee: Coffee) = coffeeDao.delete(coffee) override suspend fun delete(coffee: Coffee) = coffeeDao.delete(coffee)
} }

View File

@ -0,0 +1,16 @@
package com.zyzf.coffeepreorder.database.repository
import com.zyzf.coffeepreorder.database.dao.RemoteKeysDao
import com.zyzf.coffeepreorder.database.model.RemoteKeyType
import com.zyzf.coffeepreorder.database.model.RemoteKeys
class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository {
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
remoteKeysDao.getRemoteKeys(id, type)
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -1,17 +1,29 @@
package com.zyzf.coffeepreorder.database.repository package com.zyzf.coffeepreorder.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.dao.UserDao import com.zyzf.coffeepreorder.database.dao.UserDao
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository { class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAll(): Flow<List<User>> = userDao.getAll() override fun getAll(): Flow<PagingData<User>> = Pager(
override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password) config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = userDao::getAll
).flow
fun getAllUserPagingSource(): PagingSource<Int, User> = userDao.getAll()
override suspend fun getByUid(uid: Int): User? = userDao.getByUid(uid) override suspend fun getByUid(uid: Int): User? = userDao.getByUid(uid)
override suspend fun getLogined(): User? = userDao.getLogined() override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password)
override suspend fun setLogined(userId: Int) = userDao.setLogined(userId) override suspend fun insert(user: User): Long = userDao.insert(user)
override suspend fun logout() = userDao.logout() fun insertUsers(users: List<User>) =
override suspend fun insert(user: User) = userDao.insert(user) userDao.insert(*users.toTypedArray())
override suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int? = userDao.update(uid, login, fio, phone, password) override suspend fun update(user: User): Int? = userDao.update(user)
override suspend fun delete(uid: Int) = userDao.delete(uid) override suspend fun delete(user: User) = userDao.delete(user)
suspend fun clearUsers() = userDao.deleteAll()
} }

View File

@ -0,0 +1,10 @@
package com.zyzf.coffeepreorder.database.repository
import com.zyzf.coffeepreorder.database.model.RemoteKeyType
import com.zyzf.coffeepreorder.database.model.RemoteKeys
interface RemoteKeyRepository {
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -1,16 +1,14 @@
package com.zyzf.coffeepreorder.database.repository package com.zyzf.coffeepreorder.database.repository
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface UserRepository { interface UserRepository {
fun getAll(): Flow<List<User>> fun getAll(): Flow<PagingData<User>>
suspend fun tryLogin(login: String, password: String): User?
suspend fun getByUid(uid: Int): User? suspend fun getByUid(uid: Int): User?
suspend fun getLogined(): User? suspend fun tryLogin(login: String, password: String): User?
suspend fun setLogined(userId: Int) suspend fun insert(user: User): Long
suspend fun logout() suspend fun update(user: User) : Int?
suspend fun insert(user: User) suspend fun delete(user: User)
suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int?
suspend fun delete(uid: Int)
} }

View File

@ -7,14 +7,26 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import com.zyzf.coffeepreorder.CoffeeApplication import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.ui.cart.CartViewModel import com.zyzf.coffeepreorder.ui.cart.CartViewModel
import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel
import com.zyzf.coffeepreorder.ui.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.profile.ProfileViewModel
import com.zyzf.coffeepreorder.ui.register.RegisterViewModel
object AppViewModelProvider { object AppViewModelProvider {
val Factory = viewModelFactory { val Factory = viewModelFactory {
initializer { initializer {
CoffeeListViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository) CoffeeListViewModel(coffeeApplication().container.coffeeRestRepository, coffeeApplication().container.cartRepository)
} }
initializer { initializer {
CartViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository) CartViewModel(coffeeApplication().container.cartRepository)
}
initializer {
LoginViewModel(coffeeApplication().container.userRestRepository)
}
initializer {
RegisterViewModel(coffeeApplication().container.userRestRepository)
}
initializer {
ProfileViewModel(coffeeApplication().container.userRestRepository)
} }
} }
} }

View File

@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -95,37 +94,39 @@ fun Cart(
) )
} }
) { innerPadding -> ) { innerPadding ->
Box (modifier = Modifier.padding(0.dp).pullRefresh(state)) { Box (modifier = Modifier
.padding(innerPadding)
.pullRefresh(state)) {
PullRefreshIndicator(refreshing = refreshing, state = state, PullRefreshIndicator(refreshing = refreshing, state = state,
modifier = Modifier.zIndex(100f).align(Alignment.TopCenter) modifier = Modifier
.zIndex(100f)
.align(Alignment.TopCenter)
) )
CartList( CartList(
modifier = Modifier
.padding(innerPadding).pullRefresh(state)
.fillMaxSize(),
coffeeList = coffeeListUiState, coffeeList = coffeeListUiState,
onDeleteFromCartClick = {currentCoffee: Coffee -> onDeleteFromCartClick = {currentCoffee: Coffee ->
coffee.value = currentCoffee coffee.value = currentCoffee
openDialog.value = true openDialog.value = true
} }
) ) { coffeeId: Int ->
viewModel.getCountForCoffee(coffeeId)
}
} }
} }
DeleteFromCartAlertDialog( DeleteFromCartAlertDialog(
openDialog = openDialog, openDialog = openDialog
onConfirmClick = { ) {
coroutineScope.launch { coroutineScope.launch {
viewModel.deleteCoffeeFromCart(coffee.value) viewModel.deleteCoffeeFromCart(coffee.value)
}
} }
) }
} }
@Composable @Composable
private fun CartList( private fun CartList(
modifier: Modifier = Modifier,
coffeeList: LazyPagingItems<Coffee>, coffeeList: LazyPagingItems<Coffee>,
onDeleteFromCartClick: (coffee: Coffee) -> Unit onDeleteFromCartClick: (coffee: Coffee) -> Unit,
getCoffeeCount: (coffeeId: Int) -> Double
) { ) {
LazyColumn( LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally) { horizontalAlignment = Alignment.CenterHorizontally) {
@ -138,7 +139,8 @@ private fun CartList(
coffee?.let { coffee?.let {
CartListItem( CartListItem(
coffee = coffee, coffee = coffee,
onDeleteFromCartClick = onDeleteFromCartClick onDeleteFromCartClick = onDeleteFromCartClick,
getCoffeeCount = getCoffeeCount
) )
} }
} }
@ -148,8 +150,8 @@ private fun CartList(
@Composable @Composable
private fun CartListItem ( private fun CartListItem (
coffee: Coffee, coffee: Coffee,
modifier: Modifier = Modifier, onDeleteFromCartClick: (coffee: Coffee) -> Unit,
onDeleteFromCartClick: (coffee: Coffee) -> Unit getCoffeeCount: (coffeeId: Int) -> Double
) { ) {
Row(modifier = Modifier Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -171,8 +173,9 @@ private fun CartListItem (
Modifier Modifier
.weight(2f) .weight(2f)
.padding(start = 20.dp)) { .padding(start = 20.dp)) {
val currentCoffeeName: String = if (coffee.count > 1) { val coffeeCount: Double = getCoffeeCount(coffee.uid)
coffee.name + " x" + coffee.count.toString() val currentCoffeeName: String = if (coffeeCount > 1) {
coffee.name + " x" + coffeeCount.toString()
} else { } else {
coffee.name coffee.name
} }
@ -207,7 +210,6 @@ private fun CartListItem (
@Composable @Composable
private fun DeleteFromCartAlertDialog( private fun DeleteFromCartAlertDialog(
modifier: Modifier = Modifier,
openDialog: MutableState<Boolean>, openDialog: MutableState<Boolean>,
onConfirmClick: () -> Unit onConfirmClick: () -> Unit
) { ) {

View File

@ -4,14 +4,16 @@ import androidx.lifecycle.ViewModel
import androidx.paging.PagingData import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.repository.CartRepository import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class CartViewModel( class CartViewModel(
private val coffeeRepository: CoffeeRepository,
private val cartRepository: CartRepository private val cartRepository: CartRepository
) : ViewModel() { ) : ViewModel() {
val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAllInCart() val coffeeListUiState: Flow<PagingData<Coffee>> = cartRepository.getAllInCart()
fun getCountForCoffee(coffeeId: Int): Double {
return cartRepository.getCountForCoffee(coffeeId)
}
suspend fun deleteCoffeeFromCart(coffee: Coffee) { suspend fun deleteCoffeeFromCart(coffee: Coffee) {
cartRepository.deleteCoffee(coffee.uid, 1) cartRepository.deleteCoffee(coffee.uid, 1)

View File

@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -69,6 +68,7 @@ import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey import androidx.paging.compose.itemKey
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.ui.AppViewModelProvider import com.zyzf.coffeepreorder.ui.AppViewModelProvider
@ -89,6 +89,7 @@ fun CoffeeList(
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState()
val openDialog = remember { mutableStateOf(false) } val openDialog = remember { mutableStateOf(false) }
val coffee = remember { mutableStateOf(Coffee.getCoffee()) } val coffee = remember { mutableStateOf(Coffee.getCoffee()) }
val isImageChanged = remember { mutableStateOf(false) }
var imageUri: Any? by remember { mutableStateOf(R.drawable.img) } var imageUri: Any? by remember { mutableStateOf(R.drawable.img) }
var refreshing by remember { mutableStateOf(false) } var refreshing by remember { mutableStateOf(false) }
fun refresh() = coroutineScope.launch { fun refresh() = coroutineScope.launch {
@ -103,6 +104,7 @@ fun CoffeeList(
if (it != null) { if (it != null) {
Log.d("PhotoPicker", "Selected URI: $it") Log.d("PhotoPicker", "Selected URI: $it")
imageUri = it imageUri = it
isImageChanged.value = true
} else { } else {
Log.d("PhotoPicker", "No media selected") Log.d("PhotoPicker", "No media selected")
} }
@ -110,45 +112,47 @@ fun CoffeeList(
Scaffold( Scaffold(
topBar = {}, topBar = {},
floatingActionButton = { floatingActionButton = {
FloatingActionButton( if (CoffeeApplication.currentUser?.role == "admin") {
onClick = { FloatingActionButton(
coroutineScope.launch { onClick = {
coffee.value = Coffee.getCoffee() coroutineScope.launch {
openDialog.value = true coffee.value = Coffee.getCoffee()
} openDialog.value = true
}, }
Modifier },
.padding(all = 20.dp) Modifier
) { .padding(all = 20.dp)
Icon( ) {
imageVector = Icons.Filled.Add, Icon(
contentDescription = "Add", imageVector = Icons.Filled.Add,
modifier = Modifier.size(20.dp) contentDescription = "Add",
) modifier = Modifier.size(20.dp)
)
}
} }
} }
) { innerPadding -> ) { innerPadding ->
Box (modifier = Modifier.padding(0.dp).pullRefresh(state)) { Box (modifier = Modifier
.padding(innerPadding)
.pullRefresh(state)) {
PullRefreshIndicator(refreshing = refreshing, state = state, PullRefreshIndicator(refreshing = refreshing, state = state,
modifier = Modifier.zIndex(100f).align(Alignment.TopCenter) modifier = Modifier
.zIndex(100f)
.align(Alignment.TopCenter)
) )
CoffeeList( CoffeeList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
coffeeList = coffeeListUiState, coffeeList = coffeeListUiState,
onAddToCartClick = { coffeeUid: Int -> onAddToCartClick = { coffeeUid: Int ->
coroutineScope.launch { coroutineScope.launch {
viewModel.addCoffeeToCart(coffeeUid = coffeeUid) viewModel.addCoffeeToCart(coffeeUid = coffeeUid)
} }
},
onEditClick = { currentCoffee: Coffee ->
coroutineScope.launch {
coffee.value = currentCoffee
openDialog.value = true
}
} }
) ) { currentCoffee: Coffee ->
coroutineScope.launch {
coffee.value = currentCoffee
openDialog.value = true
}
}
} }
} }
AddEditModalBottomSheet( AddEditModalBottomSheet(
@ -162,16 +166,23 @@ fun CoffeeList(
}, },
onEditClick = { currentCoffee: Coffee, context: Context -> onEditClick = { currentCoffee: Coffee, context: Context ->
coroutineScope.launch { coroutineScope.launch {
viewModel.editCoffee(currentCoffee, imageUri as Uri, context) viewModel.editCoffee(currentCoffee, imageUri as Uri?, context)
} }
isImageChanged.value = false
}, },
onDeleteClick = { currentCoffee: Coffee -> onDeleteClick = { currentCoffee: Coffee ->
coroutineScope.launch { coroutineScope.launch {
viewModel.deleteCoffee(currentCoffee) viewModel.deleteCoffee(currentCoffee)
} }
isImageChanged.value = false
}, },
photoPicker = photoPicker, photoPicker = photoPicker,
imageUri = imageUri imageUri = imageUri,
beforeOpen = {coffeeUid: Int ->
if (coffeeUid != 0 && !isImageChanged.value) {
imageUri = Uri.parse("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.uid +".png")
}
}
) )
} }
@ -185,8 +196,10 @@ private fun AddEditModalBottomSheet(
onEditClick: (coffee: Coffee, context: Context) -> Unit, onEditClick: (coffee: Coffee, context: Context) -> Unit,
onDeleteClick: (coffee: Coffee) -> Unit, onDeleteClick: (coffee: Coffee) -> Unit,
photoPicker: ManagedActivityResultLauncher<PickVisualMediaRequest, Uri?>, photoPicker: ManagedActivityResultLauncher<PickVisualMediaRequest, Uri?>,
imageUri: Any? imageUri: Any?,
beforeOpen: (coffeeUid: Int) -> Unit
) { ) {
beforeOpen(coffee.value.uid)
var name: String by remember { mutableStateOf("")} var name: String by remember { mutableStateOf("")}
var cost: Double by remember { mutableDoubleStateOf(0.0) } var cost: Double by remember { mutableDoubleStateOf(0.0) }
var ingredients: String by remember { mutableStateOf("")} var ingredients: String by remember { mutableStateOf("")}
@ -251,22 +264,23 @@ private fun AddEditModalBottomSheet(
error = painterResource(R.drawable.ic_broken_image), error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img), placeholder = painterResource(R.drawable.loading_img),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest
.data(if (coffee.value.uid != 0) "https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.uid +".png" else imageUri) .Builder(context = LocalContext.current)
.crossfade(enable = true) .data(imageUri)
.crossfade(true)
.build(), .build(),
) )
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
if (coffee.value.uid == 0) { if (coffee.value.uid == 0) {
Button(onClick = { Button(onClick = {
onAddClick(Coffee(coffee.value.uid, name, cost, ingredients, coffee.value.cartId, coffee.value.count), context) onAddClick(Coffee(coffee.value.uid, name, cost, ingredients), context)
openDialog.value = false openDialog.value = false
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
Text("Добавить") Text("Добавить")
} }
} else { } else {
Button(onClick = { Button(onClick = {
onEditClick(Coffee(coffee.value.uid, name, cost, ingredients, coffee.value.cartId, coffee.value.count), context) onEditClick(Coffee(coffee.value.uid, name, cost, ingredients), context)
openDialog.value = false openDialog.value = false
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
Text("Изменить") Text("Изменить")
@ -287,7 +301,6 @@ private fun AddEditModalBottomSheet(
@Composable @Composable
private fun CoffeeList( private fun CoffeeList(
modifier: Modifier = Modifier,
coffeeList: LazyPagingItems<Coffee>, coffeeList: LazyPagingItems<Coffee>,
onAddToCartClick: (coffeeUid: Int) -> Unit, onAddToCartClick: (coffeeUid: Int) -> Unit,
onEditClick: (coffee: Coffee) -> Unit onEditClick: (coffee: Coffee) -> Unit
@ -301,7 +314,11 @@ private fun CoffeeList(
) {index -> ) {index ->
val coffee = coffeeList[index] val coffee = coffeeList[index]
coffee?.let { coffee?.let {
CoffeeListItem(coffee = coffee, onAddToCartClick = onAddToCartClick, onEditClick = onEditClick) CoffeeListItem(
coffee = coffee,
onAddToCartClick = onAddToCartClick,
onEditClick = onEditClick
)
} }
} }
} }
@ -310,7 +327,6 @@ private fun CoffeeList(
@Composable @Composable
private fun CoffeeListItem( private fun CoffeeListItem(
coffee: Coffee, coffee: Coffee,
modifier: Modifier = Modifier,
onAddToCartClick: (coffeeUid: Int) -> Unit, onAddToCartClick: (coffeeUid: Int) -> Unit,
onEditClick: (coffee: Coffee) -> Unit onEditClick: (coffee: Coffee) -> Unit
) { ) {
@ -321,7 +337,9 @@ private fun CoffeeListItem(
horizontalArrangement = Arrangement.SpaceAround) { horizontalArrangement = Arrangement.SpaceAround) {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png") model = ImageRequest
.Builder(context = LocalContext.current)
.data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png")
.crossfade(true).build(), .crossfade(true).build(),
error = painterResource(R.drawable.ic_broken_image), error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img), placeholder = painterResource(R.drawable.loading_img),
@ -358,18 +376,21 @@ private fun CoffeeListItem(
Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(text = "В корзину") Text(text = "В корзину")
} }
OutlinedIconButton( if (CoffeeApplication.currentUser?.role == "admin") {
onClick = { OutlinedIconButton(
onEditClick(coffee) onClick = {
}, onEditClick(coffee)
Modifier },
.padding(start = 10.dp) Modifier
.clip(CircleShape)) { .padding(start = 10.dp)
Icon( .clip(CircleShape)
imageVector = Icons.Outlined.Create, ) {
contentDescription = "Favorite", Icon(
modifier = Modifier.size(20.dp) imageVector = Icons.Outlined.Create,
) contentDescription = "Favorite",
modifier = Modifier.size(20.dp)
)
}
} }
} }
} }
@ -389,9 +410,8 @@ fun CoffeeListPreview() {
coffeeList = MutableStateFlow( coffeeList = MutableStateFlow(
PagingData.from((1..20).map { i -> Coffee.getCoffee(i) }) PagingData.from((1..20).map { i -> Coffee.getCoffee(i) })
).collectAsLazyPagingItems(), ).collectAsLazyPagingItems(),
onAddToCartClick = {}, onAddToCartClick = {}
onEditClick = {} ) {}
)
} }
} }
} }
@ -409,9 +429,8 @@ fun CoffeeEmptyListPreview() {
coffeeList = MutableStateFlow( coffeeList = MutableStateFlow(
PagingData.empty<Coffee>() PagingData.empty<Coffee>()
).collectAsLazyPagingItems(), ).collectAsLazyPagingItems(),
onAddToCartClick = {}, onAddToCartClick = {}
onEditClick = {} ) {}
)
} }
} }
} }

View File

@ -13,7 +13,6 @@ import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException import com.jcraft.jsch.JSchException
import com.jcraft.jsch.Session import com.jcraft.jsch.Session
import com.jcraft.jsch.SftpException import com.jcraft.jsch.SftpException
import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.repository.CartRepository import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
@ -29,14 +28,13 @@ class CoffeeListViewModel(
private val coffeeRepository: CoffeeRepository, private val coffeeRepository: CoffeeRepository,
private val cartRepository: CartRepository private val cartRepository: CartRepository
) : ViewModel() { ) : ViewModel() {
val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAll() val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAllCoffees()
suspend fun addCoffeeToCart(coffeeUid: Int) { suspend fun addCoffeeToCart(coffeeUid: Int) {
val cart: Cart = cartRepository.get() cartRepository.insertCoffee(coffeeUid, 1)
cart.uid?.let { cartRepository.insertCoffee(it, coffeeUid, 1) }
} }
suspend fun createCoffee(coffee: Coffee, imageUri: Uri, context: Context) { suspend fun createCoffee(coffee: Coffee, imageUri: Uri, context: Context) {
val newCoffee: Long = coffeeRepository.insert(coffee.name, coffee.cost, coffee.ingredients) val newCoffee: Long = coffeeRepository.insert(coffee)
val inputStream = context.contentResolver.openInputStream(imageUri) val inputStream = context.contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream) val bitmap = BitmapFactory.decodeStream(inputStream)
@ -54,8 +52,9 @@ class CoffeeListViewModel(
copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images") copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images")
} }
suspend fun editCoffee(coffee: Coffee, imageUri: Uri, context: Context) { suspend fun editCoffee(coffee: Coffee, imageUri: Any?, context: Context) {
val editedCoffee: Int = coffeeRepository.update(coffee.uid, coffee.name, coffee.cost, coffee.ingredients, coffee.cartId, coffee.count)!! val editedCoffee: Int = coffeeRepository.update(coffee)
if (imageUri !is Uri) return
val inputStream = context.contentResolver.openInputStream(imageUri) val inputStream = context.contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream) val bitmap = BitmapFactory.decodeStream(inputStream)

View File

@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -34,24 +35,29 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@OptIn(DelicateCoroutinesApi::class)
@Composable @Composable
fun Login(navController: NavController?) { fun Login(
navController: NavController?,
viewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val context = LocalContext.current val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var login by remember { mutableStateOf("") } var login by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -90,11 +96,11 @@ fun Login(navController: NavController?) {
Button( Button(
onClick = { onClick = {
var user: User? var user: User?
GlobalScope.launch (Dispatchers.Main) { coroutineScope.launch {
user = AppDatabase.getInstance(context).userDao().tryLogin(login, password) user = viewModel.tryLogin(login, password)
if (user != null) { if (user != null) {
AppDatabase.getInstance(context).userDao().logout() CoffeeApplication.currentUser = null
AppDatabase.getInstance(context).userDao().setLogined(user!!.uid!!) CoffeeApplication.currentUser = user
navController?.navigate(Screen.CoffeeList.route) navController?.navigate(Screen.CoffeeList.route)
} else { } else {
password = "" password = ""

View File

@ -0,0 +1,18 @@
package com.zyzf.coffeepreorder.ui.login
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.UserRepository
class LoginViewModel(
private val userRepository: UserRepository
) : ViewModel() {
suspend fun tryLogin(login: String, password: String): User? {
val user: User? = userRepository.tryLogin(login, password)
return if (user?.uid == 0) {
null
} else {
user
}
}
}

View File

@ -52,6 +52,7 @@ fun Topbar(
if ( if (
navController.previousBackStackEntry != null navController.previousBackStackEntry != null
&& (currentScreen == null || !currentScreen.showInBottomBar) && (currentScreen == null || !currentScreen.showInBottomBar)
&& currentScreen != Screen.Login
) { ) {
IconButton(onClick = { navController.navigateUp() }) { IconButton(onClick = { navController.navigateUp() }) {
Icon( Icon(

View File

@ -64,7 +64,7 @@ fun Order(navController: NavController?) {
val sum = remember { mutableStateOf(0.0) } val sum = remember { mutableStateOf(0.0) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
sum.value = AppDatabase.getInstance(context).coffeeDao().getSumInCart() sum.value = AppDatabase.getInstance(context).cartDao().getSumInCart()
} }
} }
Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) {

View File

@ -29,6 +29,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -39,12 +40,17 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -53,10 +59,14 @@ import kotlinx.coroutines.withContext
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@Composable @Composable
fun Profile(navController: NavController?) { fun Profile(
navController: NavController?,
viewModel: ProfileViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val openDialogEdit = remember { mutableStateOf(false) } val openDialogEdit = remember { mutableStateOf(false) }
val openDialogExit = remember { mutableStateOf(false) } val openDialogExit = remember { mutableStateOf(false) }
val openDialogDelete = remember { mutableStateOf(false) } val openDialogDelete = remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
var user: User by remember { mutableStateOf(User("", "", "", "", "")) } var user: User by remember { mutableStateOf(User("", "", "", "", "")) }
var userLogin by remember { mutableStateOf("") } var userLogin by remember { mutableStateOf("") }
@ -67,7 +77,7 @@ fun Profile(navController: NavController?) {
val userNewPsswdConf = remember { mutableStateOf("") } val userNewPsswdConf = remember { mutableStateOf("") }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
user = AppDatabase.getInstance(context).userDao().getLogined()!! user = CoffeeApplication.currentUser!!
userLogin = user.login userLogin = user.login
userFIO = user.fio userFIO = user.fio
userPhone = user.phone userPhone = user.phone
@ -192,11 +202,10 @@ fun Profile(navController: NavController?) {
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
GlobalScope.launch (Dispatchers.Main) { coroutineScope.launch {
if (userOldPsswd.value == user.password && userNewPsswd.value == userNewPsswdConf.value) { val user: User = viewModel.changeUser(user.password, userOldPsswd.value, userNewPsswd.value, userNewPsswd.value,
AppDatabase.getInstance(context).userDao().update(user.uid!!, userLogin, userFIO, userPhone, userNewPsswd.value) user.uid, userLogin, userFIO, userPhone, user.role)
user = AppDatabase.getInstance(context).userDao().getLogined()!! CoffeeApplication.currentUser = user
}
} }
openDialogEdit.value = false openDialogEdit.value = false
} }
@ -233,7 +242,7 @@ fun Profile(navController: NavController?) {
TextButton( TextButton(
onClick = { onClick = {
GlobalScope.launch (Dispatchers.Main) { GlobalScope.launch (Dispatchers.Main) {
AppDatabase.getInstance(context).userDao().logout() CoffeeApplication.currentUser = null
} }
openDialogExit.value = false openDialogExit.value = false
navController?.navigate(Screen.Login.route) navController?.navigate(Screen.Login.route)
@ -271,8 +280,8 @@ fun Profile(navController: NavController?) {
TextButton( TextButton(
onClick = { onClick = {
GlobalScope.launch (Dispatchers.Main) { GlobalScope.launch (Dispatchers.Main) {
AppDatabase.getInstance(context).userDao().logout() CoffeeApplication.currentUser = null
AppDatabase.getInstance(context).userDao().delete(user.uid!!) AppDatabase.getInstance(context).userDao().delete(user)
} }
openDialogDelete.value = false openDialogDelete.value = false
navController?.navigate(Screen.Login.route) navController?.navigate(Screen.Login.route)

View File

@ -0,0 +1,30 @@
package com.zyzf.coffeepreorder.ui.profile
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.UserRepository
class ProfileViewModel(
private val userRepository: UserRepository
) : ViewModel() {
suspend fun changeUser(currentUserPassw: String,
userOldPsswd: String,
userNewPsswd: String,
userNewPsswdConf: String,
userUid: Int,
userLogin: String,
userFIO: String,
userPhone: String,
userRole: String): User {
if (userOldPsswd == currentUserPassw && userNewPsswd == userNewPsswdConf) {
val userUid: Int? = userRepository.update(User(
userUid, userLogin, userFIO, userPhone, userNewPsswd, userRole))
} else {
val userUid: Int? = userRepository.update(User(
userUid, userLogin, userFIO, userPhone, currentUserPassw, userRole))
}
return userRepository.getByUid(userUid!!)!!
}
}

View File

@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -33,23 +34,26 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(DelicateCoroutinesApi::class)
@Composable @Composable
fun Register(navController: NavController?) { fun Register(
val context = LocalContext.current navController: NavController?,
viewModel: RegisterViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
var login: String by remember { mutableStateOf("") } var login: String by remember { mutableStateOf("") }
var fio: String by remember { mutableStateOf("") } var fio: String by remember { mutableStateOf("") }
var phone: String by remember { mutableStateOf("") } var phone: String by remember { mutableStateOf("") }
@ -104,13 +108,12 @@ fun Register(navController: NavController?) {
Button( Button(
onClick = { onClick = {
var user: User? var user: User?
GlobalScope.launch (Dispatchers.Main) { coroutineScope.launch (Dispatchers.Main) {
if (password == confPassword) { if (password == confPassword) {
AppDatabase.getInstance(context).userDao().insert(User(login, fio, phone, password, "user")) user = viewModel.register(User(login, fio, phone, password, "user"))
user = AppDatabase.getInstance(context).userDao().tryLogin(login, password)
if (user != null) { if (user != null) {
AppDatabase.getInstance(context).userDao().logout() CoffeeApplication.currentUser = null
AppDatabase.getInstance(context).userDao().setLogined(user!!.uid!!) CoffeeApplication.currentUser = user!!
navController?.navigate(Screen.CoffeeList.route) navController?.navigate(Screen.CoffeeList.route)
} else { } else {
password = "" password = ""

View File

@ -0,0 +1,14 @@
package com.zyzf.coffeepreorder.ui.register
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.UserRepository
class RegisterViewModel(
private val userRepository: UserRepository
) : ViewModel() {
suspend fun register(user: User): User? {
userRepository.insert(user)
return userRepository.tryLogin(user.login, user.password)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,25 +0,0 @@
<!--
~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:tint="#A9A9AC"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#A9A9AC"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z" />
</vector>

View File

@ -1,10 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources></resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -8,7 +8,6 @@
<string name="coffee_name">Название</string> <string name="coffee_name">Название</string>
<string name="coffee_cost">Стоимость</string> <string name="coffee_cost">Стоимость</string>
<string name="coffee_ingredients">Ингредиенты</string> <string name="coffee_ingredients">Ингредиенты</string>
<string name="coffee_view_title">Изменить кофе</string>
<string name="profile_title">Профиль</string> <string name="profile_title">Профиль</string>
<string name="profile_login_label">Логин</string> <string name="profile_login_label">Логин</string>
@ -19,17 +18,4 @@
<string name="profile_newpassw_label">Новый пароль</string> <string name="profile_newpassw_label">Новый пароль</string>
<string name="profile_confpassw_label">Подтверждение пароля</string> <string name="profile_confpassw_label">Подтверждение пароля</string>
<string name="student_firstname">Имя</string>
<string name="student_lastname">Фамилия</string>
<string name="student_group">Группа</string>
<string name="student_phone">Телефон</string>
<string name="student_email">e-mail</string>
<string name="student_main_title">Список студентов</string>
<string name="student_view_title">Профиль студента</string>
<string name="about_title">О нас</string>
<string name="about_text">
<p>Это текст <b>о нас</b>!</p>\n\n
<p>Здесь могла быть Ваша реклама!</p>\n\n
<p>Наш сайт <a href="https://ulstu.ru">ulstu.ru</a></p>
</string>
</resources> </resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.0.100</domain>
</domain-config>
</network-security-config>

2
backend/README.md Normal file
View File

@ -0,0 +1,2 @@
# IP_1_Kalyshev_Yan_PIbd-22

31
backend/build.gradle Normal file
View File

@ -0,0 +1,31 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.kalyshev'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
java.targetCompatibility = JavaVersion.VERSION_17

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
backend/gradlew vendored Executable file
View File

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
backend/gradlew.bat vendored Normal file
View File

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
backend/settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = 'yan'

View File

@ -0,0 +1,24 @@
package com.kalyshev.yan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@ComponentScan
public class WebConfiguration implements WebMvcConfigurer {
public static final String REST_API = "/api";
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("rest-test");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}

View File

@ -0,0 +1,11 @@
package com.kalyshev.yan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class YanApplication {
public static void main(String[] args) {
SpringApplication.run(YanApplication.class, args);
}
}

View File

@ -0,0 +1,47 @@
package com.kalyshev.yan.coffee.controller;
import com.kalyshev.yan.WebConfiguration;
import com.kalyshev.yan.coffee.service.CoffeeService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/coffee")
public class CoffeeController {
private final CoffeeService coffeeService;
public CoffeeController(CoffeeService coffeeService) {
this.coffeeService = coffeeService;
}
@GetMapping("/{id}")
public CoffeeDto getCoffee(@PathVariable Long id) {
return new CoffeeDto(coffeeService.findCoffee(id));
}
@GetMapping("/")
public List<CoffeeDto> getAllCoffees(
@RequestParam(value = "pageNo", defaultValue = "1", required = false) int pageNo,
@RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize,
@RequestParam(value = "sortBy", defaultValue = "id", required = false) String sortBy,
@RequestParam(value = "sortDir", defaultValue = "asc", required = false) String sortDir
){
return coffeeService.findAllCoffees(pageNo-1, pageSize, sortBy, sortDir);
}
@PostMapping("/")
public CoffeeDto createCoffee(@RequestBody @Valid CoffeeDto coffeeDto) {
return new CoffeeDto(coffeeService.addCoffee(coffeeDto.getName(), coffeeDto.getCost(), coffeeDto.getIngredients()));
}
@PutMapping("/{id}")
public CoffeeDto updateCoffee(@PathVariable Long id,
@RequestBody @Valid CoffeeDto coffeeDto) {
return new CoffeeDto(coffeeService.updateCoffee(id, coffeeDto.getName(), coffeeDto.getCost(), coffeeDto.getIngredients()));
}
@DeleteMapping("/{id}")
public CoffeeDto deleteCoffee(@PathVariable Long id) {
return new CoffeeDto(coffeeService.deleteCoffee(id));
}
@DeleteMapping("/")
public void deleteAllCoffees() {
coffeeService.deleteAllCoffees();
}
}

View File

@ -0,0 +1,25 @@
package com.kalyshev.yan.coffee.controller;
import com.kalyshev.yan.coffee.model.Coffee;
public class CoffeeDto {
private Long id;
private String name;
private Double cost;
private String ingredients;
public CoffeeDto() {}
public CoffeeDto(Coffee coffee) {
this.id = coffee.getId();
this.name = coffee.getName();
this.cost = coffee.getCost();
this.ingredients = coffee.getIngredients();
}
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Double getCost() {return cost;}
public void setCost(Double cost) {this.cost = cost;}
public String getIngredients() {return ingredients;}
public void setIngredients(String ingredients) {this.ingredients = ingredients;}
}

View File

@ -0,0 +1,60 @@
package com.kalyshev.yan.coffee.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Objects;
@Entity
@Table(name = "coffee")
public class Coffee {
@Id
@GeneratedValue
private Long id;
private String name;
private Double cost;
private String ingredients;
public Coffee() {
}
public Coffee(String name, Double cost, String ingredients) {
this.name = name;
this.cost = cost;
this.ingredients = ingredients;
}
public Long getId() {return id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Double getCost() {return cost;}
public void setCost(Double cost) {this.cost = cost;}
public String getIngredients() {return ingredients;}
public void setIngredients(String ingredients) {this.ingredients = ingredients;}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Coffee))
return false;
Coffee coffee = (Coffee) o;
return Objects.equals(id, coffee.id) &&
Objects.equals(this.name, coffee.name) &&
Objects.equals(this.cost, coffee.cost) &&
Objects.equals(this.ingredients, coffee.ingredients);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Coffee{" +
"id=" + id +
", name='" + name + '\'' +
", cost='" + cost + '\'' +
", ingredients='" + ingredients + '\'' +
'}';
}
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.coffee.repository;
public class CoffeeNotFoundException extends RuntimeException {
public CoffeeNotFoundException(Long id) {
super(String.format("Coffee with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.coffee.repository;
import com.kalyshev.yan.coffee.model.Coffee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
}

View File

@ -0,0 +1,90 @@
package com.kalyshev.yan.coffee.service;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.coffee.repository.CoffeeRepository;
import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.util.validation.ValidatorUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Optional;
@Service
public class CoffeeService {
private final CoffeeRepository coffeeRepository;
private final ValidatorUtil validatorUtil;
public CoffeeService(CoffeeRepository coffeeRepository,
ValidatorUtil validatorUtil) {
this.coffeeRepository = coffeeRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public Coffee addCoffee(String name, Double cost, String ingredients) {
if (!StringUtils.hasText(name)) {
throw new IllegalArgumentException("Coffee name is null or empty");
}
if (cost <= 0.0) {
throw new IllegalArgumentException("Coffee cost is null or empty or negative");
}
final Coffee coffee = new Coffee(name, cost, ingredients);
validatorUtil.validate(coffee);
return coffeeRepository.save(coffee);
}
@Transactional(readOnly = true)
public Coffee findCoffee(Long id) {
final Optional<Coffee> coffee = coffeeRepository.findById(id);
return coffee.orElseThrow(() -> new CoffeeNotFoundException(id));
}
@Transactional(readOnly = true)
public List<CoffeeDto> findAllCoffees(int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<Coffee> coffees = coffeeRepository.findAll(pageable);
// get content for page object
List<Coffee> listOfCoffees = coffees.getContent();
List<CoffeeDto> content = listOfCoffees.stream()
.map(CoffeeDto::new)
.toList();
return content;
}
@Transactional
public Coffee updateCoffee(Long id, String name, Double cost, String ingredients) {
final Coffee currentCoffee = findCoffee(id);
if (StringUtils.hasText(name)) {
currentCoffee.setName(name);
}
if (cost > 0.0) {
currentCoffee.setCost(cost);
}
if (StringUtils.hasText(ingredients)) {
currentCoffee.setIngredients(ingredients);
}
validatorUtil.validate(currentCoffee);
return coffeeRepository.save(currentCoffee);
}
@Transactional
public Coffee deleteCoffee(Long id) {
final Coffee currentCoffee = findCoffee(id);
coffeeRepository.delete(currentCoffee);
return currentCoffee;
}
@Transactional
public void deleteAllCoffees() {
coffeeRepository.deleteAll();
}
}

View File

@ -0,0 +1,56 @@
package com.kalyshev.yan.user.controller;
import com.kalyshev.yan.WebConfiguration;
import com.kalyshev.yan.user.model.User;
import com.kalyshev.yan.user.service.UserService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
return new UserDto(userService.findUser(id));
}
@GetMapping("/")
public List<UserDto> getAllUsers(
@RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo,
@RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize,
@RequestParam(value = "sortBy", defaultValue = "id", required = false) String sortBy,
@RequestParam(value = "sortDir", defaultValue = "asc", required = false) String sortDir
){
return userService.findAllUsers(pageNo, pageSize, sortBy, sortDir);
}
@GetMapping("/tryLogin")
public User tryLogin(
@RequestParam(value = "login") String login,
@RequestParam(value = "password") String password
){
return userService.tryLogin(login, password);
}
@PostMapping("/")
public UserDto createUser(@RequestBody @Valid UserDto userDto) {
return new UserDto(userService.addUser(userDto.getLogin(), userDto.getFio(), userDto.getPhone(), userDto.getPassword(), userDto.getRole()));
}
@PutMapping("/{id}")
public UserDto updateUser(@PathVariable Long id,
@RequestBody @Valid UserDto userDto) {
return new UserDto(userService.updateUser(id, userDto.getLogin(), userDto.getFio(), userDto.getPhone(), userDto.getPassword(), userDto.getRole()));
}
@DeleteMapping("/{id}")
public UserDto deleteUser(@PathVariable Long id) {
return new UserDto(userService.deleteUser(id));
}
@DeleteMapping("/")
public void deleteAllUsers() {
userService.deleteAllUsers();
}
}

View File

@ -0,0 +1,57 @@
package com.kalyshev.yan.user.controller;
import com.kalyshev.yan.user.model.User;
public class UserDto {
private Long id;
private String login;
private String fio;
private String phone;
private String password;
private String role;
public UserDto() {}
public UserDto(User user) {
this.id = user.getId();
this.login = user.getLogin();
this.fio = user.getFio();
this.phone = user.getPhone();
this.password = user.getPassword();
this.role = user.getRole();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getFio() {
return fio;
}
public void setFio(String fio) {
this.fio = fio;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}

View File

@ -0,0 +1,102 @@
package com.kalyshev.yan.user.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Objects;
@Entity
@Table(name = "user_u")
public class User {
@Id
@GeneratedValue
private Long id;
private String login;
private String fio;
private String phone;
private String password;
private String role;
public User() {
}
public User(String login, String fio, String phone, String password, String role) {
this.login = login;
this.fio = fio;
this.phone = phone;
this.password = password;
this.role = role;
}
public User(Long id, String login, String fio, String phone, String password, String role) {
this.id = id;
this.login = login;
this.fio = fio;
this.phone = phone;
this.password = password;
this.role = role;
}
public Long getId() {
return id;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getFio() {
return fio;
}
public void setFio(String fio) {
this.fio = fio;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof User))
return false;
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(this.login, user.login) &&
Objects.equals(this.fio, user.fio) &&
Objects.equals(this.phone, user.phone) &&
Objects.equals(this.password, user.password) &&
Objects.equals(this.role, user.role);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", fio='" + fio + '\'' +
", phone='" + phone + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.user.repository;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super(String.format("User with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,15 @@
package com.kalyshev.yan.user.repository;
import com.kalyshev.yan.user.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "select * from \"USER_U\" where \"LOGIN\" = :login and \"PASSWORD\" = :password", nativeQuery = true)
public Optional<User> tryLogin(@Param("login") String login,
@Param("password") String password);
}

View File

@ -0,0 +1,113 @@
package com.kalyshev.yan.user.service;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.user.controller.UserDto;
import com.kalyshev.yan.user.model.User;
import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.user.repository.UserRepository;
import com.kalyshev.yan.util.validation.ValidatorUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
private final ValidatorUtil validatorUtil;
public UserService(UserRepository userRepository,
ValidatorUtil validatorUtil) {
this.userRepository = userRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public User addUser(String login, String fio, String phone, String password, String role) {
if (!StringUtils.hasText(login)) {
throw new IllegalArgumentException("User login is null or empty");
}
if (!StringUtils.hasText(fio)) {
throw new IllegalArgumentException("User fio is null or empty");
}
if (!StringUtils.hasText(phone)) {
throw new IllegalArgumentException("User phone is null or empty");
}
if (!StringUtils.hasText(password)) {
throw new IllegalArgumentException("User password is null or empty");
}
if (!StringUtils.hasText(role)) {
throw new IllegalArgumentException("User role is null or empty");
}
final User user = new User(login, fio, phone, password, role);
validatorUtil.validate(user);
return userRepository.save(user);
}
@Transactional(readOnly = true)
public User findUser(Long id) {
final Optional<User> user = userRepository.findById(id);
return user.orElseThrow(() -> new UserNotFoundException(id));
}
@Transactional(readOnly = true)
public User tryLogin(String login, String password) {
Optional<User> user = userRepository.tryLogin(login, password);
if (user.isPresent()) {
return user.orElseThrow((() -> new UserNotFoundException((long)0))) ;
} else {
return new User((long)0,"", "", "", "", "");
}
}
@Transactional(readOnly = true)
public List<UserDto> findAllUsers(int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<User> users = userRepository.findAll(pageable);
// get content for page object
List<User> listOfUsers = users.getContent();
return listOfUsers.stream()
.map(UserDto::new)
.toList();
}
@Transactional
public User updateUser(Long id, String login, String fio, String phone, String password, String role) {
final User currentUser = findUser(id);
if (StringUtils.hasText(login)) {
currentUser.setLogin(login);
}
if (StringUtils.hasText(fio)) {
currentUser.setFio(fio);
}
if (StringUtils.hasText(phone)) {
currentUser.setPhone(phone);
}
if (StringUtils.hasText(password)) {
currentUser.setPassword(password);
}
if (StringUtils.hasText(role)) {
currentUser.setRole(role);
}
validatorUtil.validate(currentUser);
return userRepository.save(currentUser);
}
@Transactional
public User deleteUser(Long id) {
final User currentUser = findUser(id);
userRepository.delete(currentUser);
return currentUser;
}
@Transactional
public void deleteAllUsers() {
userRepository.deleteAll();
}
}

View File

@ -0,0 +1,39 @@
package com.kalyshev.yan.util.error;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.util.validation.ValidationException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;
@ControllerAdvice(annotations = RestController.class)
public class AdviceController {
@ExceptionHandler({
CoffeeNotFoundException.class,
UserNotFoundException.class,
ValidationException.class
})
public ResponseEntity<Object> handleException(Throwable e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleBindException(MethodArgumentNotValidException e) {
final ValidationException validationException = new ValidationException(
e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toSet()));
return handleException(validationException);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleUnknownException(Throwable e) {
e.printStackTrace();
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}

View File

@ -0,0 +1,9 @@
package com.kalyshev.yan.util.validation;
import java.util.Set;
public class ValidationException extends RuntimeException {
public <T> ValidationException(Set<String> errors) {
super(String.join("\n", errors));
}
}

View File

@ -0,0 +1,25 @@
package com.kalyshev.yan.util.validation;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class ValidatorUtil {
private final Validator validator;
public ValidatorUtil() {
this.validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public <T> void validate(T object) {
final Set<ConstraintViolation<T>> errors = validator.validate(object);
if (!errors.isEmpty()) {
throw new ValidationException(errors.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toSet()));
}
}
}

View File

@ -0,0 +1,11 @@
spring.main.banner-mode=off
#server.port=8080
spring.datasource.url=jdbc:h2:file:./data
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=true

View File

@ -1,6 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id("com.android.application") version "8.2.0" apply false id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.20" apply false id("org.jetbrains.kotlin.android") version "1.9.10" apply false
id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.10" apply false
} }

View File

@ -1,6 +1,6 @@
#Sun Oct 15 15:51:04 GMT+04:00 2023 #Thu Dec 14 21:41:14 GMT+04:00 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.