Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f79fd6d658 | ||
|
362cf8e347 | ||
|
2f4910bf07 | ||
|
03d07bb9da | ||
|
e4b8fb9a5f | ||
|
1819d11e39 | ||
|
ca3c675988 | ||
|
d3fde20a0a | ||
|
acd37136b0 | ||
|
042350ca59 | ||
|
cc6178a845 | ||
|
2a42e395cf | ||
|
8c7ed46049 | ||
|
d2db976f34 | ||
|
ae769e167a | ||
|
9360565c77 | ||
|
1ea8ed0e93 | ||
|
e4b1cf3daa | ||
|
bb21aa8cac | ||
|
fab50baea5 | ||
|
176147f583 | ||
|
7c3af23e3b |
42
.gitignore
vendored
42
.gitignore
vendored
@ -13,3 +13,45 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
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
3
.idea/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -1 +0,0 @@
|
||||
Coffee Preorder
|
@ -3,7 +3,20 @@
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<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>
|
||||
</value>
|
||||
</component>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-17" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
@ -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>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.20" />
|
||||
<option name="version" value="1.9.10" />
|
||||
</component>
|
||||
</project>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -2,6 +2,7 @@ plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("com.google.devtools.ksp")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -41,7 +42,7 @@ android {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.5"
|
||||
kotlinCompilerExtensionVersion = "1.5.3"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@ -56,10 +57,11 @@ kotlin {
|
||||
|
||||
dependencies {
|
||||
// Retrofit
|
||||
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.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("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
|
||||
|
||||
implementation("com.jcraft:jsch:0.1.55")
|
||||
|
||||
@ -68,30 +70,30 @@ dependencies {
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||
|
||||
// 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("androidx.navigation:navigation-compose:2.7.5")
|
||||
implementation("androidx.compose.ui:ui:1.6.0-beta02")
|
||||
implementation("androidx.compose.ui:ui-graphics:1.6.0-beta02")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta02")
|
||||
implementation("androidx.navigation:navigation-compose:2.7.6")
|
||||
implementation("androidx.compose.ui:ui:1.6.0-beta03")
|
||||
implementation("androidx.compose.ui:ui-graphics:1.6.0-beta03")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta03")
|
||||
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("eu.bambooapps:compose-material3-pullrefresh:1.0.0")
|
||||
implementation("eu.bambooapps:compose-material3-pullrefresh:1.0.1")
|
||||
|
||||
// Room
|
||||
val roomVersion = "2.6.1"
|
||||
implementation("androidx.room:room-runtime:$roomVersion")
|
||||
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
||||
ksp("androidx.room:room-compiler:$roomVersion")
|
||||
implementation("androidx.room:room-ktx:$roomVersion")
|
||||
implementation("androidx.room:room-paging:$roomVersion")
|
||||
implementation("androidx.room:room-runtime:2.6.1")
|
||||
annotationProcessor("androidx.room:room-compiler:2.6.1")
|
||||
ksp("androidx.room:room-compiler:2.6.1")
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
implementation("androidx.room:room-paging:2.6.1")
|
||||
|
||||
// Tests
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.0-beta02")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling:1.6.0-beta02")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0-beta02")
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.0-beta03")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling:1.6.0-beta03")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0-beta03")
|
||||
}
|
60
app/proguard-rules.pro
vendored
60
app/proguard-rules.pro
vendored
@ -1,21 +1,45 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
|
||||
# EnclosingMethod is required to use InnerClasses.
|
||||
-keepattributes Signature, InnerClasses, EnclosingMethod
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# Retrofit does reflection on method and parameter annotations.
|
||||
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
|
||||
-keepattributes AnnotationDefault
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
# Retain service method parameters when optimizing.
|
||||
-keepclassmembers,allowshrinking,allowobfuscation interface * {
|
||||
@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
|
@ -16,7 +16,8 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.CoffeePreorder"
|
||||
tools:targetApi="31">
|
||||
tools:targetApi="31"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
<activity
|
||||
android:name=".MainComposeActivity"
|
||||
android:exported="true"
|
||||
|
@ -3,10 +3,15 @@ package com.zyzf.coffeepreorder
|
||||
import android.app.Application
|
||||
import com.zyzf.coffeepreorder.database.AppContainer
|
||||
import com.zyzf.coffeepreorder.database.AppDataContainer
|
||||
import com.zyzf.coffeepreorder.database.model.User
|
||||
|
||||
class CoffeeApplication : Application() {
|
||||
lateinit var container: AppContainer
|
||||
|
||||
companion object {
|
||||
var currentUser: User? = null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
container = AppDataContainer(this)
|
||||
|
@ -1,15 +1,12 @@
|
||||
package com.zyzf.coffeepreorder
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.zyzf.coffeepreorder.ui.navigation.MainNavbar
|
||||
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
|
||||
|
||||
|
103
app/src/main/java/com/zyzf/coffeepreorder/api/MyServerService.kt
Normal file
103
app/src/main/java/com/zyzf/coffeepreorder/api/MyServerService.kt
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,52 @@
|
||||
package com.zyzf.coffeepreorder.database
|
||||
|
||||
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.CoffeeRepository
|
||||
import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository
|
||||
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.UserRepository
|
||||
|
||||
interface AppContainer {
|
||||
val coffeeRepository: CoffeeRepository
|
||||
val userRepository: UserRepository
|
||||
val cartRepository: CartRepository
|
||||
val coffeeRestRepository: RestCoffeeRepository
|
||||
val userRestRepository: RestUserRepository
|
||||
|
||||
companion object {
|
||||
const val LIMIT = 10
|
||||
}
|
||||
}
|
||||
|
||||
class AppDataContainer(private val context: Context) : AppContainer {
|
||||
override val coffeeRepository: CoffeeRepository by lazy {
|
||||
private val coffeeRepository: OfflineCoffeeRepository by lazy {
|
||||
OfflineCoffeeRepository(AppDatabase.getInstance(context).coffeeDao())
|
||||
}
|
||||
override val userRepository: UserRepository by lazy {
|
||||
private val userRepository: OfflineUserRepository by lazy {
|
||||
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
|
||||
}
|
||||
override val cartRepository: CartRepository by lazy {
|
||||
OfflineCartRepository(AppDatabase.getInstance(context).cartDao())
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TIMEOUT = 5000L
|
||||
const val LIMIT = 10
|
||||
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
|
||||
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
|
||||
}
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
@ -4,23 +4,21 @@ import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.zyzf.coffeepreorder.database.dao.CartDao
|
||||
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.model.Cart
|
||||
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.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 fun userDao(): UserDao
|
||||
abstract fun coffeeDao(): CoffeeDao
|
||||
abstract fun cartDao(): CartDao
|
||||
abstract fun remoteKeysDao(): RemoteKeysDao
|
||||
|
||||
companion object {
|
||||
private const val DB_NAME: String = "coffee-preorder"
|
||||
@ -36,31 +34,31 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
userDao.insert(user1)
|
||||
// Coffees
|
||||
val coffeeDao = database.coffeeDao()
|
||||
val coffee1 = Coffee("Coffee1", 200.0, "Ing1", null, 0)
|
||||
val coffee2 = Coffee("Coffee2", 200.0, "Ing1", null, 0)
|
||||
val coffee3 = Coffee("Coffee3", 300.0, "Ing1", null, 0)
|
||||
val coffee4 = Coffee("Coffee4", 200.0, "Ing1", null, 0)
|
||||
val coffee5 = Coffee("Coffee5", 200.0, "Ing1", null, 0)
|
||||
val coffee6 = Coffee("Coffee6", 25.0, "Ing1", null, 0)
|
||||
val coffee7 = Coffee("Coffee7", 400.0, "Ing1", null, 0)
|
||||
val coffee8 = Coffee("Coffee8", 200.0, "Ing1", null, 0)
|
||||
val coffee9 = Coffee("Coffee9", 200.0, "Ing1", null, 0)
|
||||
val coffee10 = Coffee("Coffee10", 900.0, "Ing1", null, 0)
|
||||
val coffee11 = Coffee("Coffee11", 200.0, "Ing1", null, 0)
|
||||
coffeeDao.insert(coffee1.name, coffee1.cost, coffee1.ingredients)
|
||||
coffeeDao.insert(coffee2.name, coffee2.cost, coffee2.ingredients)
|
||||
coffeeDao.insert(coffee3.name, coffee3.cost, coffee3.ingredients)
|
||||
coffeeDao.insert(coffee4.name, coffee4.cost, coffee4.ingredients)
|
||||
coffeeDao.insert(coffee5.name, coffee5.cost, coffee5.ingredients)
|
||||
coffeeDao.insert(coffee6.name, coffee6.cost, coffee6.ingredients)
|
||||
coffeeDao.insert(coffee7.name, coffee7.cost, coffee7.ingredients)
|
||||
coffeeDao.insert(coffee8.name, coffee8.cost, coffee8.ingredients)
|
||||
coffeeDao.insert(coffee9.name, coffee9.cost, coffee9.ingredients)
|
||||
coffeeDao.insert(coffee10.name, coffee10.cost, coffee10.ingredients)
|
||||
coffeeDao.insert(coffee11.name, coffee11.cost, coffee11.ingredients)
|
||||
val coffee1 = Coffee("Coffee1", 200.0, "Ing1")
|
||||
val coffee2 = Coffee("Coffee2", 200.0, "Ing1")
|
||||
val coffee3 = Coffee("Coffee3", 300.0, "Ing1")
|
||||
val coffee4 = Coffee("Coffee4", 200.0, "Ing1")
|
||||
val coffee5 = Coffee("Coffee5", 200.0, "Ing1")
|
||||
val coffee6 = Coffee("Coffee6", 25.0, "Ing1")
|
||||
val coffee7 = Coffee("Coffee7", 400.0, "Ing1")
|
||||
val coffee8 = Coffee("Coffee8", 200.0, "Ing1")
|
||||
val coffee9 = Coffee("Coffee9", 200.0, "Ing1")
|
||||
val coffee10 = Coffee("Coffee10", 900.0, "Ing1")
|
||||
val coffee11 = Coffee("Coffee11", 200.0, "Ing1")
|
||||
coffeeDao.insert(coffee1)
|
||||
coffeeDao.insert(coffee2)
|
||||
coffeeDao.insert(coffee3)
|
||||
coffeeDao.insert(coffee4)
|
||||
coffeeDao.insert(coffee5)
|
||||
coffeeDao.insert(coffee6)
|
||||
coffeeDao.insert(coffee7)
|
||||
coffeeDao.insert(coffee8)
|
||||
coffeeDao.insert(coffee9)
|
||||
coffeeDao.insert(coffee10)
|
||||
coffeeDao.insert(coffee11)
|
||||
// Cart
|
||||
val cartDao = database.cartDao()
|
||||
val cart = Cart()
|
||||
val cart = Cart(2, 1)
|
||||
cartDao.insert(cart)
|
||||
}
|
||||
}
|
||||
@ -72,14 +70,15 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
AppDatabase::class.java,
|
||||
DB_NAME
|
||||
)
|
||||
.addCallback(object : Callback() {
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
super.onCreate(db)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
populateDatabase()
|
||||
}
|
||||
}
|
||||
})
|
||||
// .addCallback(object : Callback() {
|
||||
// override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
// super.onCreate(db)
|
||||
// CoroutineScope(Dispatchers.IO).launch {
|
||||
// populateDatabase()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
.also { INSTANCE = it }
|
||||
}
|
||||
|
@ -1,23 +1,48 @@
|
||||
package com.zyzf.coffeepreorder.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import com.zyzf.coffeepreorder.database.model.Cart
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
|
||||
@Dao
|
||||
interface CartDao {
|
||||
@Query("select * from cart limit 1")
|
||||
suspend fun get(): Cart
|
||||
@Query("select * from 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
|
||||
suspend fun insert(cart: Cart)
|
||||
|
||||
@Query("update coffee set cart_id = :cartId, count = count + :count where uid = :coffeeId")
|
||||
suspend fun insertCoffee(cartId: Int, coffeeId: Int, count: Int)
|
||||
@Query("select * from cart where coffee_id = :coffeeId limit 1")
|
||||
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)
|
||||
|
||||
@Update
|
||||
|
@ -3,30 +3,31 @@ package com.zyzf.coffeepreorder.database.dao
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
|
||||
|
||||
@Dao
|
||||
interface CoffeeDao {
|
||||
@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")
|
||||
fun getAllInCart(): PagingSource<Int, Coffee>
|
||||
@Query("select coffee.uid, name, cost, ingredients from coffee where coffee.uid = :uid")
|
||||
suspend fun getByUid(uid: Int): Coffee?
|
||||
|
||||
@Query("select sum(cost) from coffee where cart_id is not null and count > 0")
|
||||
fun getSumInCart(): Double
|
||||
@Insert
|
||||
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")
|
||||
suspend fun getByUid(uid: Int): CoffeeWithCart?
|
||||
@Insert
|
||||
suspend fun insert(vararg coffee: Coffee)
|
||||
|
||||
@Query("insert into coffee (name, cost, ingredients, count) values (:name, :cost, :ingredients, 0)")
|
||||
suspend fun insert(name: String, cost: Double, ingredients: String): Long
|
||||
|
||||
@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?
|
||||
@Update
|
||||
fun update(coffee: Coffee): Int
|
||||
|
||||
@Delete
|
||||
suspend fun delete(coffee: Coffee)
|
||||
|
||||
@Query("delete from coffee")
|
||||
suspend fun deleteAll()
|
||||
}
|
@ -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)
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
package com.zyzf.coffeepreorder.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.zyzf.coffeepreorder.database.model.User
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface UserDao {
|
||||
@Query("select * from user")
|
||||
fun getAll(): Flow<List<User>>
|
||||
fun getAll(): PagingSource<Int, User>
|
||||
|
||||
@Query("select * from user where login = :login and password = :password")
|
||||
suspend fun tryLogin(login: String, password: String): User?
|
||||
@ -17,21 +19,18 @@ interface UserDao {
|
||||
@Query("select * from user where uid = :uid")
|
||||
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")
|
||||
suspend fun getLogined(): User?
|
||||
|
||||
@Query("insert into user_logined (user_id) values (:userId)")
|
||||
suspend fun setLogined(userId: Int)
|
||||
|
||||
@Query("delete from user_logined")
|
||||
suspend fun logout()
|
||||
@Insert
|
||||
fun insert(user: User): Long
|
||||
|
||||
@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")
|
||||
suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int?
|
||||
@Update
|
||||
fun update(user: User): Int?
|
||||
|
||||
@Query("delete from user where uid = :uid")
|
||||
suspend fun delete(uid: Int)
|
||||
@Delete
|
||||
suspend fun delete(user: User)
|
||||
|
||||
@Query("delete from user")
|
||||
suspend fun deleteAll()
|
||||
}
|
@ -1,25 +1,47 @@
|
||||
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 = "cart")
|
||||
@Entity(tableName = "cart", foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Cart::class,
|
||||
parentColumns = ["uid"],
|
||||
childColumns = ["coffee_id"],
|
||||
onDelete = ForeignKey.RESTRICT,
|
||||
onUpdate = ForeignKey.RESTRICT
|
||||
)
|
||||
])
|
||||
data class Cart(
|
||||
@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
|
||||
constructor() : this(null)
|
||||
constructor(
|
||||
coffeeId: Int,
|
||||
count: Int
|
||||
) : this(0, coffeeId, count)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
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 {
|
||||
return uid ?: -1
|
||||
var result = uid
|
||||
result = 31 * result + coffeeId.hashCode()
|
||||
result = 31 * result + count.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,10 @@ 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 = "coffee", foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Cart::class,
|
||||
parentColumns = ["uid"],
|
||||
childColumns = ["cart_id"],
|
||||
onDelete = ForeignKey.RESTRICT,
|
||||
onUpdate = ForeignKey.RESTRICT
|
||||
)
|
||||
])
|
||||
@Entity(tableName = "coffee")
|
||||
data class Coffee(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val uid: Int = 0,
|
||||
@ -23,20 +14,14 @@ data class Coffee(
|
||||
@ColumnInfo(name = "cost")
|
||||
var cost: Double,
|
||||
@ColumnInfo(name = "ingredients")
|
||||
var ingredients: String,
|
||||
@ColumnInfo(name = "cart_id", index = true)
|
||||
val cartId: Int?,
|
||||
@ColumnInfo(name = "count")
|
||||
val count: Int = 0
|
||||
var ingredients: String
|
||||
) {
|
||||
@Ignore
|
||||
constructor(
|
||||
name: String,
|
||||
cost: Double,
|
||||
ingredients: String,
|
||||
cartId: Int?,
|
||||
count: Int?
|
||||
) : this(0, name, cost, ingredients, null, 0)
|
||||
ingredients: String
|
||||
) : this(0, name, cost, ingredients)
|
||||
|
||||
companion object {
|
||||
fun getCoffee(index: Int = 0): Coffee {
|
||||
@ -44,9 +29,7 @@ data class Coffee(
|
||||
index,
|
||||
"",
|
||||
0.0,
|
||||
"",
|
||||
null,
|
||||
0
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -55,10 +38,17 @@ data class Coffee(
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
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 {
|
||||
return uid
|
||||
var result = uid
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + cost.hashCode()
|
||||
result = 31 * result + ingredients.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
@ -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?
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ import androidx.room.PrimaryKey
|
||||
@Entity(tableName = "user")
|
||||
data class User(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val uid: Int?,
|
||||
val uid: Int = 0,
|
||||
@ColumnInfo(name = "login")
|
||||
var login: String,
|
||||
@ColumnInfo(name = "fio")
|
||||
@ -27,16 +27,40 @@ data class User(
|
||||
phone: String,
|
||||
password: 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 {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,12 +1,18 @@
|
||||
package com.zyzf.coffeepreorder.database.repository
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.zyzf.coffeepreorder.database.model.Cart
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CartRepository {
|
||||
suspend fun get(): Cart
|
||||
suspend fun getAll(): 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)
|
||||
fun getCountForCoffee(coffeeId: Int): Double
|
||||
suspend fun update(cart: Cart)
|
||||
suspend fun deleteAll()
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,12 @@ package com.zyzf.coffeepreorder.database.repository
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CoffeeRepository {
|
||||
fun getAll(): Flow<PagingData<Coffee>>
|
||||
fun getAllInCart(): Flow<PagingData<Coffee>>
|
||||
fun getSumInCart(): Double
|
||||
suspend fun getByUid(uid: Int): CoffeeWithCart?
|
||||
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?
|
||||
fun getAllCoffees(): Flow<PagingData<Coffee>>
|
||||
suspend fun getByUid(uid: Int): Coffee?
|
||||
suspend fun insert(coffee: Coffee): Long
|
||||
suspend fun update(coffee: Coffee): Int
|
||||
suspend fun delete(coffee: Coffee)
|
||||
}
|
@ -1,13 +1,28 @@
|
||||
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.model.Cart
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
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 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 fun getCountForCoffee(coffeeId: Int): Double = cartDao.getCountForCoffee(coffeeId)
|
||||
override suspend fun update(cart: Cart) = cartDao.update(cart)
|
||||
override suspend fun deleteAll() = cartDao.deleteAll()
|
||||
}
|
||||
}
|
||||
|
@ -3,30 +3,27 @@ package com.zyzf.coffeepreorder.database.repository
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
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.model.Coffee
|
||||
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository {
|
||||
override fun getAll(): Flow<PagingData<Coffee>> = Pager(
|
||||
override fun getAllCoffees(): Flow<PagingData<Coffee>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = AppDataContainer.LIMIT,
|
||||
pageSize = AppContainer.LIMIT,
|
||||
enablePlaceholders = false
|
||||
),
|
||||
pagingSourceFactory = coffeeDao::getAll
|
||||
pagingSourceFactory = coffeeDao::getAllCoffees
|
||||
).flow
|
||||
override fun getAllInCart(): Flow<PagingData<Coffee>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = AppDataContainer.LIMIT,
|
||||
enablePlaceholders = false
|
||||
),
|
||||
pagingSourceFactory = coffeeDao::getAllInCart
|
||||
).flow
|
||||
override fun getSumInCart(): Double = coffeeDao.getSumInCart()
|
||||
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)
|
||||
|
||||
fun getAllCoffeesPagingSource(): PagingSource<Int, Coffee> = coffeeDao.getAllCoffees()
|
||||
suspend fun clearCoffees() = coffeeDao.deleteAll()
|
||||
override suspend fun getByUid(uid: Int): Coffee? = coffeeDao.getByUid(uid)
|
||||
override suspend fun insert(coffee: Coffee): Long = coffeeDao.insert(coffee)
|
||||
suspend fun insertCoffees(coffees: List<Coffee>) =
|
||||
coffeeDao.insert(*coffees.toTypedArray())
|
||||
override suspend fun update(coffee: Coffee): Int = coffeeDao.update(coffee)
|
||||
override suspend fun delete(coffee: Coffee) = coffeeDao.delete(coffee)
|
||||
}
|
@ -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)
|
||||
}
|
@ -1,17 +1,29 @@
|
||||
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.model.User
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
|
||||
override fun getAll(): Flow<List<User>> = userDao.getAll()
|
||||
override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password)
|
||||
override fun getAll(): Flow<PagingData<User>> = Pager(
|
||||
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 getLogined(): User? = userDao.getLogined()
|
||||
override suspend fun setLogined(userId: Int) = userDao.setLogined(userId)
|
||||
override suspend fun logout() = userDao.logout()
|
||||
override suspend fun insert(user: User) = userDao.insert(user)
|
||||
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 delete(uid: Int) = userDao.delete(uid)
|
||||
override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password)
|
||||
override suspend fun insert(user: User): Long = userDao.insert(user)
|
||||
fun insertUsers(users: List<User>) =
|
||||
userDao.insert(*users.toTypedArray())
|
||||
override suspend fun update(user: User): Int? = userDao.update(user)
|
||||
override suspend fun delete(user: User) = userDao.delete(user)
|
||||
suspend fun clearUsers() = userDao.deleteAll()
|
||||
}
|
@ -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)
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
package com.zyzf.coffeepreorder.database.repository
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.zyzf.coffeepreorder.database.model.User
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface UserRepository {
|
||||
fun getAll(): Flow<List<User>>
|
||||
suspend fun tryLogin(login: String, password: String): User?
|
||||
fun getAll(): Flow<PagingData<User>>
|
||||
suspend fun getByUid(uid: Int): User?
|
||||
suspend fun getLogined(): User?
|
||||
suspend fun setLogined(userId: Int)
|
||||
suspend fun logout()
|
||||
suspend fun insert(user: User)
|
||||
suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int?
|
||||
suspend fun delete(uid: Int)
|
||||
suspend fun tryLogin(login: String, password: String): User?
|
||||
suspend fun insert(user: User): Long
|
||||
suspend fun update(user: User) : Int?
|
||||
suspend fun delete(user: User)
|
||||
}
|
@ -7,14 +7,26 @@ import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.zyzf.coffeepreorder.CoffeeApplication
|
||||
import com.zyzf.coffeepreorder.ui.cart.CartViewModel
|
||||
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 {
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
CoffeeListViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository)
|
||||
CoffeeListViewModel(coffeeApplication().container.coffeeRestRepository, coffeeApplication().container.cartRepository)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -95,37 +94,39 @@ fun Cart(
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box (modifier = Modifier.padding(0.dp).pullRefresh(state)) {
|
||||
Box (modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(state)) {
|
||||
PullRefreshIndicator(refreshing = refreshing, state = state,
|
||||
modifier = Modifier.zIndex(100f).align(Alignment.TopCenter)
|
||||
modifier = Modifier
|
||||
.zIndex(100f)
|
||||
.align(Alignment.TopCenter)
|
||||
)
|
||||
CartList(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding).pullRefresh(state)
|
||||
.fillMaxSize(),
|
||||
coffeeList = coffeeListUiState,
|
||||
onDeleteFromCartClick = {currentCoffee: Coffee ->
|
||||
coffee.value = currentCoffee
|
||||
openDialog.value = true
|
||||
}
|
||||
)
|
||||
) { coffeeId: Int ->
|
||||
viewModel.getCountForCoffee(coffeeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
DeleteFromCartAlertDialog(
|
||||
openDialog = openDialog,
|
||||
onConfirmClick = {
|
||||
coroutineScope.launch {
|
||||
viewModel.deleteCoffeeFromCart(coffee.value)
|
||||
}
|
||||
openDialog = openDialog
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
viewModel.deleteCoffeeFromCart(coffee.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CartList(
|
||||
modifier: Modifier = Modifier,
|
||||
coffeeList: LazyPagingItems<Coffee>,
|
||||
onDeleteFromCartClick: (coffee: Coffee) -> Unit
|
||||
onDeleteFromCartClick: (coffee: Coffee) -> Unit,
|
||||
getCoffeeCount: (coffeeId: Int) -> Double
|
||||
) {
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
@ -138,7 +139,8 @@ private fun CartList(
|
||||
coffee?.let {
|
||||
CartListItem(
|
||||
coffee = coffee,
|
||||
onDeleteFromCartClick = onDeleteFromCartClick
|
||||
onDeleteFromCartClick = onDeleteFromCartClick,
|
||||
getCoffeeCount = getCoffeeCount
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -148,8 +150,8 @@ private fun CartList(
|
||||
@Composable
|
||||
private fun CartListItem (
|
||||
coffee: Coffee,
|
||||
modifier: Modifier = Modifier,
|
||||
onDeleteFromCartClick: (coffee: Coffee) -> Unit
|
||||
onDeleteFromCartClick: (coffee: Coffee) -> Unit,
|
||||
getCoffeeCount: (coffeeId: Int) -> Double
|
||||
) {
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@ -171,8 +173,9 @@ private fun CartListItem (
|
||||
Modifier
|
||||
.weight(2f)
|
||||
.padding(start = 20.dp)) {
|
||||
val currentCoffeeName: String = if (coffee.count > 1) {
|
||||
coffee.name + " x" + coffee.count.toString()
|
||||
val coffeeCount: Double = getCoffeeCount(coffee.uid)
|
||||
val currentCoffeeName: String = if (coffeeCount > 1) {
|
||||
coffee.name + " x" + coffeeCount.toString()
|
||||
} else {
|
||||
coffee.name
|
||||
}
|
||||
@ -207,7 +210,6 @@ private fun CartListItem (
|
||||
|
||||
@Composable
|
||||
private fun DeleteFromCartAlertDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
openDialog: MutableState<Boolean>,
|
||||
onConfirmClick: () -> Unit
|
||||
) {
|
||||
|
@ -4,14 +4,16 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.PagingData
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import com.zyzf.coffeepreorder.database.repository.CartRepository
|
||||
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class CartViewModel(
|
||||
private val coffeeRepository: CoffeeRepository,
|
||||
private val cartRepository: CartRepository
|
||||
) : 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) {
|
||||
cartRepository.deleteCoffee(coffee.uid, 1)
|
||||
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -69,6 +68,7 @@ import androidx.paging.compose.itemContentType
|
||||
import androidx.paging.compose.itemKey
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.zyzf.coffeepreorder.CoffeeApplication
|
||||
import com.zyzf.coffeepreorder.R
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
|
||||
@ -89,6 +89,7 @@ fun CoffeeList(
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
val openDialog = remember { mutableStateOf(false) }
|
||||
val coffee = remember { mutableStateOf(Coffee.getCoffee()) }
|
||||
val isImageChanged = remember { mutableStateOf(false) }
|
||||
var imageUri: Any? by remember { mutableStateOf(R.drawable.img) }
|
||||
var refreshing by remember { mutableStateOf(false) }
|
||||
fun refresh() = coroutineScope.launch {
|
||||
@ -103,6 +104,7 @@ fun CoffeeList(
|
||||
if (it != null) {
|
||||
Log.d("PhotoPicker", "Selected URI: $it")
|
||||
imageUri = it
|
||||
isImageChanged.value = true
|
||||
} else {
|
||||
Log.d("PhotoPicker", "No media selected")
|
||||
}
|
||||
@ -110,45 +112,47 @@ fun CoffeeList(
|
||||
Scaffold(
|
||||
topBar = {},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
coffee.value = Coffee.getCoffee()
|
||||
openDialog.value = true
|
||||
}
|
||||
},
|
||||
Modifier
|
||||
.padding(all = 20.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Add",
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
if (CoffeeApplication.currentUser?.role == "admin") {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
coffee.value = Coffee.getCoffee()
|
||||
openDialog.value = true
|
||||
}
|
||||
},
|
||||
Modifier
|
||||
.padding(all = 20.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Add",
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box (modifier = Modifier.padding(0.dp).pullRefresh(state)) {
|
||||
Box (modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(state)) {
|
||||
PullRefreshIndicator(refreshing = refreshing, state = state,
|
||||
modifier = Modifier.zIndex(100f).align(Alignment.TopCenter)
|
||||
modifier = Modifier
|
||||
.zIndex(100f)
|
||||
.align(Alignment.TopCenter)
|
||||
)
|
||||
CoffeeList(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
coffeeList = coffeeListUiState,
|
||||
onAddToCartClick = { coffeeUid: Int ->
|
||||
coroutineScope.launch {
|
||||
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(
|
||||
@ -162,16 +166,23 @@ fun CoffeeList(
|
||||
},
|
||||
onEditClick = { currentCoffee: Coffee, context: Context ->
|
||||
coroutineScope.launch {
|
||||
viewModel.editCoffee(currentCoffee, imageUri as Uri, context)
|
||||
viewModel.editCoffee(currentCoffee, imageUri as Uri?, context)
|
||||
}
|
||||
isImageChanged.value = false
|
||||
},
|
||||
onDeleteClick = { currentCoffee: Coffee ->
|
||||
coroutineScope.launch {
|
||||
viewModel.deleteCoffee(currentCoffee)
|
||||
}
|
||||
isImageChanged.value = false
|
||||
},
|
||||
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,
|
||||
onDeleteClick: (coffee: Coffee) -> Unit,
|
||||
photoPicker: ManagedActivityResultLauncher<PickVisualMediaRequest, Uri?>,
|
||||
imageUri: Any?
|
||||
imageUri: Any?,
|
||||
beforeOpen: (coffeeUid: Int) -> Unit
|
||||
) {
|
||||
beforeOpen(coffee.value.uid)
|
||||
var name: String by remember { mutableStateOf("")}
|
||||
var cost: Double by remember { mutableDoubleStateOf(0.0) }
|
||||
var ingredients: String by remember { mutableStateOf("")}
|
||||
@ -251,22 +264,23 @@ private fun AddEditModalBottomSheet(
|
||||
error = painterResource(R.drawable.ic_broken_image),
|
||||
placeholder = painterResource(R.drawable.loading_img),
|
||||
contentScale = ContentScale.Crop,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(if (coffee.value.uid != 0) "https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.uid +".png" else imageUri)
|
||||
.crossfade(enable = true)
|
||||
model = ImageRequest
|
||||
.Builder(context = LocalContext.current)
|
||||
.data(imageUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
)
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||
if (coffee.value.uid == 0) {
|
||||
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
|
||||
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
|
||||
Text("Добавить")
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
|
||||
Text("Изменить")
|
||||
@ -287,7 +301,6 @@ private fun AddEditModalBottomSheet(
|
||||
|
||||
@Composable
|
||||
private fun CoffeeList(
|
||||
modifier: Modifier = Modifier,
|
||||
coffeeList: LazyPagingItems<Coffee>,
|
||||
onAddToCartClick: (coffeeUid: Int) -> Unit,
|
||||
onEditClick: (coffee: Coffee) -> Unit
|
||||
@ -301,7 +314,11 @@ private fun CoffeeList(
|
||||
) {index ->
|
||||
val coffee = coffeeList[index]
|
||||
coffee?.let {
|
||||
CoffeeListItem(coffee = coffee, onAddToCartClick = onAddToCartClick, onEditClick = onEditClick)
|
||||
CoffeeListItem(
|
||||
coffee = coffee,
|
||||
onAddToCartClick = onAddToCartClick,
|
||||
onEditClick = onEditClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -310,7 +327,6 @@ private fun CoffeeList(
|
||||
@Composable
|
||||
private fun CoffeeListItem(
|
||||
coffee: Coffee,
|
||||
modifier: Modifier = Modifier,
|
||||
onAddToCartClick: (coffeeUid: Int) -> Unit,
|
||||
onEditClick: (coffee: Coffee) -> Unit
|
||||
) {
|
||||
@ -321,7 +337,9 @@ private fun CoffeeListItem(
|
||||
horizontalArrangement = Arrangement.SpaceAround) {
|
||||
|
||||
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(),
|
||||
error = painterResource(R.drawable.ic_broken_image),
|
||||
placeholder = painterResource(R.drawable.loading_img),
|
||||
@ -358,18 +376,21 @@ private fun CoffeeListItem(
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(text = "В корзину")
|
||||
}
|
||||
OutlinedIconButton(
|
||||
onClick = {
|
||||
onEditClick(coffee)
|
||||
},
|
||||
Modifier
|
||||
.padding(start = 10.dp)
|
||||
.clip(CircleShape)) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Create,
|
||||
contentDescription = "Favorite",
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
if (CoffeeApplication.currentUser?.role == "admin") {
|
||||
OutlinedIconButton(
|
||||
onClick = {
|
||||
onEditClick(coffee)
|
||||
},
|
||||
Modifier
|
||||
.padding(start = 10.dp)
|
||||
.clip(CircleShape)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Create,
|
||||
contentDescription = "Favorite",
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -389,9 +410,8 @@ fun CoffeeListPreview() {
|
||||
coffeeList = MutableStateFlow(
|
||||
PagingData.from((1..20).map { i -> Coffee.getCoffee(i) })
|
||||
).collectAsLazyPagingItems(),
|
||||
onAddToCartClick = {},
|
||||
onEditClick = {}
|
||||
)
|
||||
onAddToCartClick = {}
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,9 +429,8 @@ fun CoffeeEmptyListPreview() {
|
||||
coffeeList = MutableStateFlow(
|
||||
PagingData.empty<Coffee>()
|
||||
).collectAsLazyPagingItems(),
|
||||
onAddToCartClick = {},
|
||||
onEditClick = {}
|
||||
)
|
||||
onAddToCartClick = {}
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ import com.jcraft.jsch.JSch
|
||||
import com.jcraft.jsch.JSchException
|
||||
import com.jcraft.jsch.Session
|
||||
import com.jcraft.jsch.SftpException
|
||||
import com.zyzf.coffeepreorder.database.model.Cart
|
||||
import com.zyzf.coffeepreorder.database.model.Coffee
|
||||
import com.zyzf.coffeepreorder.database.repository.CartRepository
|
||||
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
|
||||
@ -29,14 +28,13 @@ class CoffeeListViewModel(
|
||||
private val coffeeRepository: CoffeeRepository,
|
||||
private val cartRepository: CartRepository
|
||||
) : ViewModel() {
|
||||
val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAll()
|
||||
val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAllCoffees()
|
||||
|
||||
suspend fun addCoffeeToCart(coffeeUid: Int) {
|
||||
val cart: Cart = cartRepository.get()
|
||||
cart.uid?.let { cartRepository.insertCoffee(it, coffeeUid, 1) }
|
||||
cartRepository.insertCoffee(coffeeUid, 1)
|
||||
}
|
||||
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 bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
|
||||
@ -54,8 +52,9 @@ class CoffeeListViewModel(
|
||||
|
||||
copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images")
|
||||
}
|
||||
suspend fun editCoffee(coffee: Coffee, imageUri: Uri, context: Context) {
|
||||
val editedCoffee: Int = coffeeRepository.update(coffee.uid, coffee.name, coffee.cost, coffee.ingredients, coffee.cartId, coffee.count)!!
|
||||
suspend fun editCoffee(coffee: Coffee, imageUri: Any?, context: Context) {
|
||||
val editedCoffee: Int = coffeeRepository.update(coffee)
|
||||
if (imageUri !is Uri) return
|
||||
val inputStream = context.contentResolver.openInputStream(imageUri)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
|
||||
|
@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -34,24 +35,29 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.zyzf.coffeepreorder.CoffeeApplication
|
||||
import com.zyzf.coffeepreorder.R
|
||||
import com.zyzf.coffeepreorder.database.AppDatabase
|
||||
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.theme.CoffeePreorderTheme
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@Composable
|
||||
fun Login(navController: NavController?) {
|
||||
fun Login(
|
||||
navController: NavController?,
|
||||
viewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var login by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
LaunchedEffect(Unit) {
|
||||
@ -90,11 +96,11 @@ fun Login(navController: NavController?) {
|
||||
Button(
|
||||
onClick = {
|
||||
var user: User?
|
||||
GlobalScope.launch (Dispatchers.Main) {
|
||||
user = AppDatabase.getInstance(context).userDao().tryLogin(login, password)
|
||||
coroutineScope.launch {
|
||||
user = viewModel.tryLogin(login, password)
|
||||
if (user != null) {
|
||||
AppDatabase.getInstance(context).userDao().logout()
|
||||
AppDatabase.getInstance(context).userDao().setLogined(user!!.uid!!)
|
||||
CoffeeApplication.currentUser = null
|
||||
CoffeeApplication.currentUser = user
|
||||
navController?.navigate(Screen.CoffeeList.route)
|
||||
} else {
|
||||
password = ""
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@ fun Topbar(
|
||||
if (
|
||||
navController.previousBackStackEntry != null
|
||||
&& (currentScreen == null || !currentScreen.showInBottomBar)
|
||||
&& currentScreen != Screen.Login
|
||||
) {
|
||||
IconButton(onClick = { navController.navigateUp() }) {
|
||||
Icon(
|
||||
|
@ -64,7 +64,7 @@ fun Order(navController: NavController?) {
|
||||
val sum = remember { mutableStateOf(0.0) }
|
||||
LaunchedEffect(Unit) {
|
||||
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) {
|
||||
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -39,12 +40,17 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.zyzf.coffeepreorder.CoffeeApplication
|
||||
import com.zyzf.coffeepreorder.R
|
||||
import com.zyzf.coffeepreorder.database.AppDatabase
|
||||
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.theme.CoffeePreorderTheme
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -53,10 +59,14 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@Composable
|
||||
fun Profile(navController: NavController?) {
|
||||
fun Profile(
|
||||
navController: NavController?,
|
||||
viewModel: ProfileViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||
) {
|
||||
val openDialogEdit = remember { mutableStateOf(false) }
|
||||
val openDialogExit = remember { mutableStateOf(false) }
|
||||
val openDialogDelete = remember { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
var user: User by remember { mutableStateOf(User("", "", "", "", "")) }
|
||||
var userLogin by remember { mutableStateOf("") }
|
||||
@ -67,7 +77,7 @@ fun Profile(navController: NavController?) {
|
||||
val userNewPsswdConf = remember { mutableStateOf("") }
|
||||
LaunchedEffect(Unit) {
|
||||
withContext(Dispatchers.IO) {
|
||||
user = AppDatabase.getInstance(context).userDao().getLogined()!!
|
||||
user = CoffeeApplication.currentUser!!
|
||||
userLogin = user.login
|
||||
userFIO = user.fio
|
||||
userPhone = user.phone
|
||||
@ -192,11 +202,10 @@ fun Profile(navController: NavController?) {
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
GlobalScope.launch (Dispatchers.Main) {
|
||||
if (userOldPsswd.value == user.password && userNewPsswd.value == userNewPsswdConf.value) {
|
||||
AppDatabase.getInstance(context).userDao().update(user.uid!!, userLogin, userFIO, userPhone, userNewPsswd.value)
|
||||
user = AppDatabase.getInstance(context).userDao().getLogined()!!
|
||||
}
|
||||
coroutineScope.launch {
|
||||
val user: User = viewModel.changeUser(user.password, userOldPsswd.value, userNewPsswd.value, userNewPsswd.value,
|
||||
user.uid, userLogin, userFIO, userPhone, user.role)
|
||||
CoffeeApplication.currentUser = user
|
||||
}
|
||||
openDialogEdit.value = false
|
||||
}
|
||||
@ -233,7 +242,7 @@ fun Profile(navController: NavController?) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
GlobalScope.launch (Dispatchers.Main) {
|
||||
AppDatabase.getInstance(context).userDao().logout()
|
||||
CoffeeApplication.currentUser = null
|
||||
}
|
||||
openDialogExit.value = false
|
||||
navController?.navigate(Screen.Login.route)
|
||||
@ -271,8 +280,8 @@ fun Profile(navController: NavController?) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
GlobalScope.launch (Dispatchers.Main) {
|
||||
AppDatabase.getInstance(context).userDao().logout()
|
||||
AppDatabase.getInstance(context).userDao().delete(user.uid!!)
|
||||
CoffeeApplication.currentUser = null
|
||||
AppDatabase.getInstance(context).userDao().delete(user)
|
||||
}
|
||||
openDialogDelete.value = false
|
||||
navController?.navigate(Screen.Login.route)
|
||||
|
@ -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!!)!!
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -33,23 +34,26 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.zyzf.coffeepreorder.CoffeeApplication
|
||||
import com.zyzf.coffeepreorder.R
|
||||
import com.zyzf.coffeepreorder.database.AppDatabase
|
||||
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.theme.CoffeePreorderTheme
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@Composable
|
||||
fun Register(navController: NavController?) {
|
||||
val context = LocalContext.current
|
||||
fun Register(
|
||||
navController: NavController?,
|
||||
viewModel: RegisterViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var login: String by remember { mutableStateOf("") }
|
||||
var fio: String by remember { mutableStateOf("") }
|
||||
var phone: String by remember { mutableStateOf("") }
|
||||
@ -104,13 +108,12 @@ fun Register(navController: NavController?) {
|
||||
Button(
|
||||
onClick = {
|
||||
var user: User?
|
||||
GlobalScope.launch (Dispatchers.Main) {
|
||||
coroutineScope.launch (Dispatchers.Main) {
|
||||
if (password == confPassword) {
|
||||
AppDatabase.getInstance(context).userDao().insert(User(login, fio, phone, password, "user"))
|
||||
user = AppDatabase.getInstance(context).userDao().tryLogin(login, password)
|
||||
user = viewModel.register(User(login, fio, phone, password, "user"))
|
||||
if (user != null) {
|
||||
AppDatabase.getInstance(context).userDao().logout()
|
||||
AppDatabase.getInstance(context).userDao().setLogined(user!!.uid!!)
|
||||
CoffeeApplication.currentUser = null
|
||||
CoffeeApplication.currentUser = user!!
|
||||
navController?.navigate(Screen.CoffeeList.route)
|
||||
} else {
|
||||
password = ""
|
||||
|
@ -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 |
@ -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>
|
@ -1,10 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<resources></resources>
|
@ -8,7 +8,6 @@
|
||||
<string name="coffee_name">Название</string>
|
||||
<string name="coffee_cost">Стоимость</string>
|
||||
<string name="coffee_ingredients">Ингредиенты</string>
|
||||
<string name="coffee_view_title">Изменить кофе</string>
|
||||
|
||||
<string name="profile_title">Профиль</string>
|
||||
<string name="profile_login_label">Логин</string>
|
||||
@ -19,17 +18,4 @@
|
||||
<string name="profile_newpassw_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>
|
6
app/src/main/res/xml/network_security_config.xml
Normal file
6
app/src/main/res/xml/network_security_config.xml
Normal 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
2
backend/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# IP_1_Kalyshev_Yan_PIbd-22
|
||||
|
31
backend/build.gradle
Normal file
31
backend/build.gradle
Normal 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
|
BIN
backend/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
backend/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
backend/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
backend/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
249
backend/gradlew
vendored
Executable 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
92
backend/gradlew.bat
vendored
Normal 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
1
backend/settings.gradle
Normal file
@ -0,0 +1 @@
|
||||
rootProject.name = 'yan'
|
24
backend/src/main/java/com/kalyshev/yan/WebConfiguration.java
Normal file
24
backend/src/main/java/com/kalyshev/yan/WebConfiguration.java
Normal 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("*");
|
||||
}
|
||||
}
|
11
backend/src/main/java/com/kalyshev/yan/YanApplication.java
Normal file
11
backend/src/main/java/com/kalyshev/yan/YanApplication.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;}
|
||||
}
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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> {
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
102
backend/src/main/java/com/kalyshev/yan/user/model/User.java
Normal file
102
backend/src/main/java/com/kalyshev/yan/user/model/User.java
Normal 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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
11
backend/src/main/resources/application.properties
Normal file
11
backend/src/main/resources/application.properties
Normal 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
|
@ -1,6 +1,7 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
|
||||
id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.10" 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
|
||||
}
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
BIN
Отчеты/Отчет мобилки 1.docx
Normal file
BIN
Отчеты/Отчет мобилки 1.docx
Normal file
Binary file not shown.
BIN
Отчеты/Отчет мобилки 2.docx
Normal file
BIN
Отчеты/Отчет мобилки 2.docx
Normal file
Binary file not shown.
BIN
Отчеты/Отчет мобилки 3.docx
Normal file
BIN
Отчеты/Отчет мобилки 3.docx
Normal file
Binary file not shown.
BIN
Отчеты/Отчет мобилки 4.docx
Normal file
BIN
Отчеты/Отчет мобилки 4.docx
Normal file
Binary file not shown.
BIN
Отчеты/Отчет мобилки 5.docx
Normal file
BIN
Отчеты/Отчет мобилки 5.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user