Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
118e8a0ea2 | |||
8c81b83717 | |||
756cfa14c0 | |||
7f9450d02e | |||
750e362438 |
@ -1,6 +1,10 @@
|
|||||||
|
import org.apache.tools.ant.util.JavaEnvUtils.VERSION_11
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
id("org.jetbrains.kotlin.plugin.serialization")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -23,21 +27,24 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.4.3"
|
kotlinCompilerExtensionVersion = "1.4.5"
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
@ -46,16 +53,41 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Core
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
implementation("androidx.activity:activity-compose:1.7.0")
|
|
||||||
|
// UI
|
||||||
|
implementation("androidx.activity:activity-compose:1.7.2")
|
||||||
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||||
|
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
|
|
||||||
|
// Room
|
||||||
|
val room_version = "2.5.2"
|
||||||
|
implementation("androidx.room:room-runtime:$room_version")
|
||||||
|
annotationProcessor("androidx.room:room-compiler:$room_version")
|
||||||
|
ksp("androidx.room:room-compiler:$room_version")
|
||||||
|
implementation("androidx.room:room-ktx:$room_version")
|
||||||
|
implementation("androidx.room:room-paging:$room_version")
|
||||||
|
|
||||||
|
// retrofit
|
||||||
|
val retrofitVersion = "2.9.0"
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
|
||||||
|
implementation("androidx.paging:paging-compose:3.2.1")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
|
||||||
|
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
|
||||||
|
|
||||||
|
// Tests
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
@ -63,4 +95,6 @@ dependencies {
|
|||||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -2,7 +2,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".FoodWarriorsApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@ -11,7 +14,8 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.MyApplication"
|
android:theme="@style/Theme.MyApplication"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.ContentProviderCompat.requireContext
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.createSavedStateHandle
|
||||||
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import com.example.myapplication.database.PrefRepository
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishListViewModel
|
||||||
|
import com.example.myapplication.ui.dishes.view.CategoryDropDownViewModel
|
||||||
|
import com.example.myapplication.ui.dishes.view.DishEditViewModel
|
||||||
|
import com.example.myapplication.ui.dishes.view.DishViewModel
|
||||||
|
import com.example.myapplication.ui.extra.ErrorsViewModel
|
||||||
|
import com.example.myapplication.ui.user.UserViewModel
|
||||||
|
|
||||||
|
|
||||||
|
object AppViewModelProvider {
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
DishListViewModel(foodWarriorsApplication().container.dishRestRepository,
|
||||||
|
foodWarriorsApplication().container.userFavoritesRestRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
ErrorsViewModel()
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
UserViewModel(foodWarriorsApplication().container.userRestRepository)
|
||||||
|
}
|
||||||
|
// initializer {
|
||||||
|
// DishViewModel(foodWarriorsApplication().container.dishRestRepository)
|
||||||
|
// }
|
||||||
|
// initializer {
|
||||||
|
// DishEditViewModel(
|
||||||
|
// this.createSavedStateHandle(),
|
||||||
|
// foodWarriorsApplication().container.dishRestRepository
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// initializer {
|
||||||
|
// CategoryDropDownViewModel(foodWarriorsApplication().container.categoryRestRepository)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CreationExtras.foodWarriorsApplication(): FoodWarriorsApplication =
|
||||||
|
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as FoodWarriorsApplication)
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import com.example.myapplication.database.AppContainer
|
||||||
|
import com.example.myapplication.database.AppDataContainer
|
||||||
|
|
||||||
|
class FoodWarriorsApplication : Application() {
|
||||||
|
lateinit var container: AppContainer
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
container = AppDataContainer(this)
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +1,40 @@
|
|||||||
package com.example.myapplication
|
package com.example.myapplication
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
import com.example.myapplication.ui.navigation.MainNavbar
|
||||||
import com.example.myapplication.ui.theme.MyApplicationTheme
|
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
MyApplicationTheme {
|
MyApplicationTheme {
|
||||||
// A surface container using the 'background' color from the theme
|
Surface(
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
modifier = Modifier.fillMaxSize(),
|
||||||
Greeting("Android")
|
color = MaterialTheme.colorScheme.background
|
||||||
}
|
) {
|
||||||
}
|
MainNavbar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
}
|
||||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
|
||||||
Text(
|
|
||||||
text = "Hello $name!",
|
|
||||||
modifier = modifier
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun GreetingPreview() {
|
|
||||||
MyApplicationTheme {
|
|
||||||
Greeting("Android")
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
object ApiRoutes {
|
||||||
|
const val BASE = "http://10.0.2.2:8083/"
|
||||||
|
const val PREFIX = "api/"
|
||||||
|
}
|
134
app/src/main/java/com/example/myapplication/api/ServerService.kt
Normal file
134
app/src/main/java/com/example/myapplication/api/ServerService.kt
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package com.example.myapplication.api
|
||||||
|
|
||||||
|
import com.example.myapplication.api.model.CategoryRemote
|
||||||
|
import com.example.myapplication.api.model.DishRemote
|
||||||
|
import com.example.myapplication.api.model.UserFavoritesRemote
|
||||||
|
import com.example.myapplication.api.model.UserRemote
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.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 ServerService {
|
||||||
|
@POST(ApiRoutes.PREFIX + "users")
|
||||||
|
suspend fun insertUser(
|
||||||
|
@Body user: UserRemote
|
||||||
|
)
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "users")
|
||||||
|
suspend fun getAllUsers() : List<UserRemote>
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "user/{id}")
|
||||||
|
suspend fun getUserById(
|
||||||
|
@Path("id") id: Int
|
||||||
|
): UserRemote?
|
||||||
|
|
||||||
|
@PUT(ApiRoutes.PREFIX + "user/{id}")
|
||||||
|
suspend fun updateUser(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
@Body user: UserRemote
|
||||||
|
)
|
||||||
|
|
||||||
|
@DELETE(ApiRoutes.PREFIX + "user/{id}")
|
||||||
|
suspend fun deleteUser(
|
||||||
|
@Path("id") id: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "categories")
|
||||||
|
suspend fun getCategories() : List<CategoryRemote>
|
||||||
|
|
||||||
|
@POST(ApiRoutes.PREFIX + "categories")
|
||||||
|
suspend fun insertCategory(
|
||||||
|
@Body category: CategoryRemote
|
||||||
|
)
|
||||||
|
|
||||||
|
@POST(ApiRoutes.PREFIX + "dishes")
|
||||||
|
suspend fun insertDish(
|
||||||
|
@Body dish: DishRemote
|
||||||
|
)
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "dishes")
|
||||||
|
suspend fun getAllDishes(): List<DishRemote>
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "user_dish/{id}")
|
||||||
|
suspend fun getAllUserDishes(
|
||||||
|
@Path("id") userId: Int
|
||||||
|
): List<DishRemote>
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "dish/{id}")
|
||||||
|
suspend fun getDish(
|
||||||
|
@Path("id") dishId: Int
|
||||||
|
): DishRemote
|
||||||
|
|
||||||
|
@PUT(ApiRoutes.PREFIX + "dish/{id}")
|
||||||
|
suspend fun editDish(
|
||||||
|
@Path("id") dishId: Int,
|
||||||
|
@Body dish: DishRemote
|
||||||
|
)
|
||||||
|
|
||||||
|
@DELETE(ApiRoutes.PREFIX + "dish/{id}")
|
||||||
|
suspend fun deleteDish(
|
||||||
|
@Path("id") dishId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@GET(ApiRoutes.PREFIX + "favorites/{id}")
|
||||||
|
suspend fun getFavoritesOfUser(
|
||||||
|
@Path("id") userId: Int
|
||||||
|
): List<DishRemote>
|
||||||
|
|
||||||
|
@DELETE(ApiRoutes.PREFIX + "favorite/{userId}/{dishId}")
|
||||||
|
suspend fun deleteFavorite(
|
||||||
|
@Path("userId") userId: Int,
|
||||||
|
@Path("dishId") dishId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@POST(ApiRoutes.PREFIX + "favorite/{userId}/{dishId}")
|
||||||
|
suspend fun createFavorite(
|
||||||
|
@Path("userId") userId: Int,
|
||||||
|
@Path("dishId") dishId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@DELETE(ApiRoutes.PREFIX + "favorite/{userId}/{dishId}")
|
||||||
|
suspend fun getUserFavorite(
|
||||||
|
@Path("userId") userId: Int,
|
||||||
|
@Path("dishId") dishId: Int,
|
||||||
|
) : UserFavoritesRemote
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val BASE_URL = ApiRoutes.BASE
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: ServerService? = null
|
||||||
|
|
||||||
|
fun getInstance(): ServerService {
|
||||||
|
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(ServerService::class.java)
|
||||||
|
.also { INSTANCE = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.myapplication.api.model
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CategoryRemote(
|
||||||
|
val uid: Int = 0,
|
||||||
|
val name: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CategoryRemote.toCategory(): Category = Category(
|
||||||
|
uid = uid,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Category.toCategoryRemote(): CategoryRemote = CategoryRemote(
|
||||||
|
uid = uid!!,
|
||||||
|
name = name
|
||||||
|
)
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.example.myapplication.api.model
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DishRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val name: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val image: String?,
|
||||||
|
val userId: Int = 0,
|
||||||
|
val categoryId: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun DishRemote.toDish(): Dish = Dish(
|
||||||
|
uid = id,
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
image = ByteArray(1),
|
||||||
|
userId = userId,
|
||||||
|
categoryId = categoryId
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fun Dish.toDishRemote(): DishRemote = DishRemote(
|
||||||
|
id = uid!!,
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
image = image.toString(),
|
||||||
|
userId = userId!!,
|
||||||
|
categoryId = categoryId!!
|
||||||
|
)
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.example.myapplication.api.model
|
||||||
|
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.UserFavorites
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserFavoritesRemote(
|
||||||
|
val userId: Int = 0,
|
||||||
|
val dishId: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun UserFavoritesRemote.toUserFavorites(): UserFavorites = UserFavorites(
|
||||||
|
userId = userId,
|
||||||
|
dishId = dishId
|
||||||
|
)
|
||||||
|
|
||||||
|
fun UserFavorites.toRemote(): UserFavoritesRemote = UserFavoritesRemote(
|
||||||
|
userId = userId,
|
||||||
|
dishId = dishId
|
||||||
|
)
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.api.model
|
||||||
|
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserRemote(
|
||||||
|
val id: Int = 0,
|
||||||
|
val nickname: String = "",
|
||||||
|
val email: String = "",
|
||||||
|
val hashed_password: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun UserRemote.toUser(): User = User(
|
||||||
|
uid = id,
|
||||||
|
nickname = nickname,
|
||||||
|
email = email,
|
||||||
|
password = hashed_password
|
||||||
|
)
|
||||||
|
|
||||||
|
fun User.toRemote(): UserRemote = UserRemote(
|
||||||
|
id = uid!!,
|
||||||
|
nickname = nickname,
|
||||||
|
email = email,
|
||||||
|
hashed_password = password
|
||||||
|
)
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.example.myapplication.api.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.api.ServerService
|
||||||
|
import com.example.myapplication.api.model.toCategory
|
||||||
|
import com.example.myapplication.api.model.toCategoryRemote
|
||||||
|
import com.example.myapplication.database.dao.CategoryDao
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import com.example.myapplication.database.repository.CategoryRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineCategoryRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class RestCategoryRepository(
|
||||||
|
private val service: ServerService,
|
||||||
|
private val dbCategoryRepository: OfflineCategoryRepository,
|
||||||
|
) : CategoryRepository {
|
||||||
|
override suspend fun getAll(): List<Category> {
|
||||||
|
dbCategoryRepository.deleteAll()
|
||||||
|
val categories = service.getCategories().map { it.toCategory() }
|
||||||
|
categories.forEach() {dbCategoryRepository.insert(it)}
|
||||||
|
return categories
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insert(category: Category) {
|
||||||
|
service.insertCategory(category.toCategoryRemote())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.example.myapplication.api.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.api.ServerService
|
||||||
|
import com.example.myapplication.api.model.toDish
|
||||||
|
import com.example.myapplication.api.model.toDishRemote
|
||||||
|
import com.example.myapplication.database.dao.DishDao
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import com.example.myapplication.database.repository.DishRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineCategoryRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineDishRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class RestDishRepository(private val service: ServerService,
|
||||||
|
private val dbCategoryRepository: OfflineDishRepository
|
||||||
|
) : DishRepository {
|
||||||
|
override suspend fun getAllDishes(): List<Dish> {
|
||||||
|
return service.getAllDishes().map { it.toDish() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDish(uid: Int): Dish? {
|
||||||
|
return service.getDish(uid).toDish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUserDishes(userUid: Int): List<Dish> {
|
||||||
|
return service.getAllUserDishes(userUid).map { it.toDish() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertDish(dish: Dish) {
|
||||||
|
service.insertDish(dish.toDishRemote())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateDish(dish: Dish) {
|
||||||
|
service.editDish(dish.uid!!, dish.toDishRemote())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteDish(dish: Dish) {
|
||||||
|
service.deleteDish(dish.uid!!)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.example.myapplication.api.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.api.ServerService
|
||||||
|
import com.example.myapplication.api.model.toDish
|
||||||
|
import com.example.myapplication.api.model.toUserFavorites
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.UserFavorites
|
||||||
|
import com.example.myapplication.database.repository.OfflineUserFavoritesRepository
|
||||||
|
import com.example.myapplication.database.repository.UserWithFavoritesRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
class RestUserFavoritesRepository(
|
||||||
|
private val service: ServerService,
|
||||||
|
private val dbCategoryRepository: OfflineUserFavoritesRepository,
|
||||||
|
) : UserWithFavoritesRepository {
|
||||||
|
override suspend fun getUserFavorites(userUid: Int): List<Dish> {
|
||||||
|
return service.getFavoritesOfUser(userUid).map { it.toDish() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites? {
|
||||||
|
return service.getUserFavorite(userUid, dishUid).toUserFavorites()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUserFavorites(userUid: Int, dishUid: Int) {
|
||||||
|
service.deleteFavorite(userUid, dishUid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUserFavorites(userUid: Int, dishUid: Int) {
|
||||||
|
service.createFavorite(userUid, dishUid)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.example.myapplication.api.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.api.ServerService
|
||||||
|
import com.example.myapplication.api.model.toRemote
|
||||||
|
import com.example.myapplication.api.model.toUser
|
||||||
|
import com.example.myapplication.api.model.toUserFavorites
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.database.repository.OfflineUserRepository
|
||||||
|
import com.example.myapplication.database.repository.UserRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
|
class RestUserRepository(
|
||||||
|
private val service: ServerService,
|
||||||
|
private val dbCategoryRepository: OfflineUserRepository
|
||||||
|
) : UserRepository {
|
||||||
|
override suspend fun getAllUsers(): List<User> {
|
||||||
|
return service.getAllUsers().map { it.toUser() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUser(uid: Int): User? {
|
||||||
|
try {
|
||||||
|
return service.getUserById(uid)!!.toUser()
|
||||||
|
}
|
||||||
|
catch (e: HttpException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUser(user: User) {
|
||||||
|
service.insertUser(user.toRemote())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateUser(user: User) {
|
||||||
|
service.updateUser(user.uid!!, user.toRemote())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUser(user: User) {
|
||||||
|
service.deleteUser(user.uid!!)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package com.example.myapplication.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.example.myapplication.api.ServerService
|
||||||
|
import com.example.myapplication.api.repository.RestCategoryRepository
|
||||||
|
import com.example.myapplication.api.repository.RestDishRepository
|
||||||
|
import com.example.myapplication.api.repository.RestUserFavoritesRepository
|
||||||
|
import com.example.myapplication.api.repository.RestUserRepository
|
||||||
|
import com.example.myapplication.database.repository.CategoryRepository
|
||||||
|
import com.example.myapplication.database.repository.DishRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineCategoryRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineDishRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineUserFavoritesRepository
|
||||||
|
import com.example.myapplication.database.repository.OfflineUserRepository
|
||||||
|
import com.example.myapplication.database.repository.UserRepository
|
||||||
|
import com.example.myapplication.database.repository.UserWithFavoritesRepository
|
||||||
|
|
||||||
|
|
||||||
|
interface AppContainer {
|
||||||
|
val userRestRepository : UserRepository
|
||||||
|
val dishRestRepository : DishRepository
|
||||||
|
val categoryRestRepository : CategoryRepository
|
||||||
|
val userFavoritesRestRepository: UserWithFavoritesRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppDataContainer(private val context: Context) : AppContainer {
|
||||||
|
|
||||||
|
val userRepository: OfflineUserRepository by lazy {
|
||||||
|
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
|
||||||
|
}
|
||||||
|
|
||||||
|
val dishRepository: OfflineDishRepository by lazy {
|
||||||
|
OfflineDishRepository(AppDatabase.getInstance(context).dishDao())
|
||||||
|
}
|
||||||
|
|
||||||
|
val categoryRepository: OfflineCategoryRepository by lazy {
|
||||||
|
OfflineCategoryRepository(AppDatabase.getInstance(context).categoryDao())
|
||||||
|
}
|
||||||
|
|
||||||
|
val userFavoritesRepository: OfflineUserFavoritesRepository by lazy {
|
||||||
|
OfflineUserFavoritesRepository(AppDatabase.getInstance(context).userFavoritesDao())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val userFavoritesRestRepository: UserWithFavoritesRepository by lazy {
|
||||||
|
RestUserFavoritesRepository(
|
||||||
|
service = ServerService.getInstance(),
|
||||||
|
dbCategoryRepository = userFavoritesRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val userRestRepository: UserRepository by lazy {
|
||||||
|
RestUserRepository(
|
||||||
|
service = ServerService.getInstance(),
|
||||||
|
dbCategoryRepository = userRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val dishRestRepository: DishRepository by lazy {
|
||||||
|
RestDishRepository(
|
||||||
|
service = ServerService.getInstance(),
|
||||||
|
dbCategoryRepository = dishRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val categoryRestRepository: CategoryRepository by lazy {
|
||||||
|
RestCategoryRepository(
|
||||||
|
service = ServerService.getInstance(),
|
||||||
|
dbCategoryRepository = categoryRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TIMEOUT = 5000L
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package com.example.myapplication.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Context.MODE_PRIVATE
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import com.example.myapplication.database.dao.CategoryDao
|
||||||
|
import com.example.myapplication.database.dao.DishDao
|
||||||
|
import com.example.myapplication.database.dao.UserDao
|
||||||
|
import com.example.myapplication.database.dao.UserFavoritesDao
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.database.model.UserFavorites
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [
|
||||||
|
Dish::class,
|
||||||
|
User::class,
|
||||||
|
Category::class,
|
||||||
|
UserFavorites::class],
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun dishDao() : DishDao
|
||||||
|
abstract fun userDao() : UserDao
|
||||||
|
abstract fun categoryDao() : CategoryDao
|
||||||
|
abstract fun userFavoritesDao() : UserFavoritesDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DB_NAME: String = "food_warriors.db"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: AppDatabase? = null
|
||||||
|
|
||||||
|
private suspend fun populateDatabase() {
|
||||||
|
INSTANCE?.let { database ->
|
||||||
|
// Groups
|
||||||
|
val categoryDao = database.categoryDao()
|
||||||
|
val category = Category(1, "Pizza")
|
||||||
|
val category1 = Category(2, "Sushi")
|
||||||
|
categoryDao.insert(category)
|
||||||
|
categoryDao.insert(category1)
|
||||||
|
// Students
|
||||||
|
val userDao = database.userDao()
|
||||||
|
val user = User(1, "Nick1", "1@1.1", "123")
|
||||||
|
val user1 = User(2, "Nick2", "1@1.1", "234")
|
||||||
|
val user2 = User(3, "Nick3", "1@1.1", "345")
|
||||||
|
val user3 = User(4, "Nick4", "1@1.1", "456")
|
||||||
|
val user4 = User(5, "Nick5", "1@1.1", "567")
|
||||||
|
userDao.insert(user)
|
||||||
|
userDao.insert(user1)
|
||||||
|
userDao.insert(user2)
|
||||||
|
userDao.insert(user3)
|
||||||
|
userDao.insert(user4)
|
||||||
|
val dishDao = database.dishDao()
|
||||||
|
val dish = Dish(1, "Peperoni", "Lorem ipsum", null, user, category)
|
||||||
|
val dish1 = Dish(2, "Fish roll", "Lorem ipsum", null, user, category1)
|
||||||
|
val dish2 = Dish(3, "Lazani", "Lorem ipsum", null, user1, category)
|
||||||
|
dishDao.insert(dish)
|
||||||
|
dishDao.insert(dish1)
|
||||||
|
dishDao.insert(dish2)
|
||||||
|
|
||||||
|
val userFavoritesDao = database.userFavoritesDao()
|
||||||
|
val userFavorites = UserFavorites(user, dish2)
|
||||||
|
val userFavorites1 = UserFavorites(user3, dish1)
|
||||||
|
val userFavorites2 = UserFavorites(user3, dish2)
|
||||||
|
val userFavorites3 = UserFavorites(user1, dish)
|
||||||
|
userFavoritesDao.insert(userFavorites)
|
||||||
|
userFavoritesDao.insert(userFavorites1)
|
||||||
|
userFavoritesDao.insert(userFavorites2)
|
||||||
|
userFavoritesDao.insert(userFavorites3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(appContext: Context): AppDatabase {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
Room.databaseBuilder(
|
||||||
|
appContext,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
DB_NAME
|
||||||
|
)
|
||||||
|
.addCallback(object : Callback() {
|
||||||
|
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||||
|
super.onCreate(db)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
populateDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.also { INSTANCE = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.example.myapplication.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
class PrefRepository(context: Context) {
|
||||||
|
private val pref: SharedPreferences = context.getSharedPreferences("FoodWarPref", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
val editor = pref.edit()
|
||||||
|
fun loadId(id: Int) {
|
||||||
|
editor.putInt("user_id", id)
|
||||||
|
editor.commit()
|
||||||
|
}
|
||||||
|
fun getId() = pref.getInt("user_id", 2)
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.database.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CategoryDao {
|
||||||
|
@Query("select * from categories order by category_name collate nocase asc")
|
||||||
|
suspend fun getAll(): List<Category>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(category: Category)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(category: Category)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(category: Category)
|
||||||
|
|
||||||
|
@Query("DELETE FROM categories")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.example.myapplication.database.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DishDao {
|
||||||
|
@Query("select * from dishes order by dish_name collate nocase asc")
|
||||||
|
suspend fun getAll(): List<Dish>
|
||||||
|
|
||||||
|
@Query("select * from dishes left join categories on dishes.category_id = categories.category_id " +
|
||||||
|
"left join users on dishes.user_id = users.user_id where dishes.dish_id = :uid")
|
||||||
|
suspend fun getByUid(uid: Int): Dish?
|
||||||
|
|
||||||
|
@Query("select * from dishes where dishes.user_id = :uid")
|
||||||
|
suspend fun getAllOFUser(uid: Int): List<Dish>
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(category: Dish)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(category: Dish)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(category: Dish)
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.example.myapplication.database.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.database.model.UserWithFavorites
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserDao {
|
||||||
|
@Query("select * from users")
|
||||||
|
suspend fun getAll(): List<User>
|
||||||
|
|
||||||
|
@Query("select * from users where users.user_id = :uid")
|
||||||
|
suspend fun getByUid(uid: Int): User?
|
||||||
|
|
||||||
|
|
||||||
|
@Query("select * from user_favorites left join users on user_favorites.user_id = users.user_id " +
|
||||||
|
"left join dishes on user_favorites.dish_id = dishes.dish_id WHERE user_favorites.user_id = :uid")
|
||||||
|
suspend fun getFavorites(uid: Int): List<UserWithFavorites>
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(user: User)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(user: User)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(user: User)
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.example.myapplication.database.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.database.model.UserFavorites
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserFavoritesDao {
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(userFavorites: UserFavorites)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(userFavorites: UserFavorites)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(userFavorites: UserFavorites)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM dishes")
|
||||||
|
suspend fun getUserWithFavorites(): List<Dish>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM user_favorites us JOIN dishes d ON d.dish_id = us.dish_id WHERE us.user_id = :uid")
|
||||||
|
suspend fun getUserFavorites(uid: Int?): List<Dish>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM user_favorites us WHERE us.user_id = :userUid AND us.dish_id = :dishUid")
|
||||||
|
suspend fun getUserFavorite(userUid: Int?, dishUid: Int?): UserFavorites?
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.example.myapplication.database.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
//enum class Category(
|
||||||
|
// val value: String,
|
||||||
|
//) {
|
||||||
|
// CHICKEN("Chicken"),
|
||||||
|
// BEEF("Beef"),
|
||||||
|
// SOUP("Soup"),
|
||||||
|
// DESSERT("Dessert"),
|
||||||
|
// VEGETARIAN("Vegetarian"),
|
||||||
|
// MILK("Milk"),
|
||||||
|
// VEGAN("Vegan"),
|
||||||
|
// PIZZA("Pizza"),
|
||||||
|
// DONUT("Donut");
|
||||||
|
//
|
||||||
|
// companion object {
|
||||||
|
// val allDishCategories = listOf(
|
||||||
|
// CHICKEN, BEEF, SOUP, DESSERT, VEGETARIAN, MILK, VEGAN, PIZZA, DONUT
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// fun getDishCategory(value: String): Category? {
|
||||||
|
// val map = Category.values().associateBy(Category::value)
|
||||||
|
// return map[value]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
@Entity(tableName="categories")
|
||||||
|
data class Category(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name="category_id")
|
||||||
|
val uid: Int?,
|
||||||
|
@ColumnInfo(name = "category_name")
|
||||||
|
val name: String
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as Category
|
||||||
|
if (uid != other.uid) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uid ?: -1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.example.myapplication.database.model
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
//data class Dish (
|
||||||
|
// val name: String,
|
||||||
|
// val description: Int,
|
||||||
|
// val author: User,
|
||||||
|
// val category: Category,
|
||||||
|
// val favorite: Boolean,
|
||||||
|
// val image: Int?
|
||||||
|
//) : Serializable
|
||||||
|
|
||||||
|
//fun getAllDishes(): List<Dish> {
|
||||||
|
// return listOf(
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
@Entity(tableName = "dishes", foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = User::class,
|
||||||
|
parentColumns = ["user_id"],
|
||||||
|
childColumns = ["user_id"],
|
||||||
|
onDelete = ForeignKey.NO_ACTION,
|
||||||
|
onUpdate = ForeignKey.NO_ACTION
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = Category::class,
|
||||||
|
parentColumns = ["category_id"],
|
||||||
|
childColumns = ["category_id"],
|
||||||
|
onDelete = ForeignKey.NO_ACTION,
|
||||||
|
onUpdate = ForeignKey.NO_ACTION
|
||||||
|
)
|
||||||
|
])
|
||||||
|
data class Dish(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = "dish_id")
|
||||||
|
val uid: Int?,
|
||||||
|
@ColumnInfo(name = "dish_name")
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
|
val image: ByteArray?,
|
||||||
|
@ColumnInfo(name="user_id", index = true)
|
||||||
|
val userId: Int?,
|
||||||
|
@ColumnInfo(name="category_id", index = true)
|
||||||
|
val categoryId: Int?
|
||||||
|
) {
|
||||||
|
@Ignore
|
||||||
|
constructor(
|
||||||
|
uid: Int?,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
image: ByteArray?,
|
||||||
|
user: User,
|
||||||
|
category: Category
|
||||||
|
) : this (uid, name, description, image, user.uid, category.uid)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as Dish
|
||||||
|
if (uid != other.uid) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uid ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBitmapFromByteArray(): Bitmap? {
|
||||||
|
if (image == null) return null
|
||||||
|
return BitmapFactory.decodeByteArray(image, 0, image.size)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.example.myapplication.database.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Embedded
|
||||||
|
|
||||||
|
data class DishWithCategoryAndUser(
|
||||||
|
@Embedded
|
||||||
|
val dish: Dish?,
|
||||||
|
@ColumnInfo(name="nickname")
|
||||||
|
val nickname: String?,
|
||||||
|
@ColumnInfo(name="category_name")
|
||||||
|
val categoryName: String?,
|
||||||
|
)
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.example.myapplication.database.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
//data class User (
|
||||||
|
// val nickname: String,
|
||||||
|
// val email: String,
|
||||||
|
// val password: String
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//val firstUser = User("UserNickname1", "UserEmail1", "UserPassword1")
|
||||||
|
//val secondUser = User("UserNickname2", "UserEmail2", "UserPassword2")
|
||||||
|
//fun getAllUsers(): List<User> {
|
||||||
|
// return listOf(
|
||||||
|
// firstUser, secondUser
|
||||||
|
// )
|
||||||
|
//}
|
||||||
|
|
||||||
|
@Entity(tableName = "users")
|
||||||
|
data class User(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name="user_id")
|
||||||
|
val uid: Int?,
|
||||||
|
val nickname: String,
|
||||||
|
val email: String,
|
||||||
|
val password: String,
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as User
|
||||||
|
if (uid != other.uid) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uid ?: -1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.example.myapplication.database.model;
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Ignore
|
||||||
|
|
||||||
|
@Entity(tableName = "user_favorites", primaryKeys = ["user_id", "dish_id"])
|
||||||
|
data class UserFavorites(
|
||||||
|
@ColumnInfo(name = "user_id")
|
||||||
|
val userId: Int,
|
||||||
|
@ColumnInfo(name="dish_id")
|
||||||
|
val dishId: Int
|
||||||
|
) {
|
||||||
|
@Ignore constructor(
|
||||||
|
user: User,
|
||||||
|
dish: Dish
|
||||||
|
) : this (user.uid!!, dish.uid!!)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.example.myapplication.database.model
|
||||||
|
|
||||||
|
import androidx.room.Embedded
|
||||||
|
import androidx.room.Junction
|
||||||
|
import androidx.room.Relation
|
||||||
|
|
||||||
|
data class FavoriteWithUsers(
|
||||||
|
@Embedded
|
||||||
|
val dish: Dish,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "dish_id",
|
||||||
|
entityColumn = "user_id",
|
||||||
|
associateBy = Junction(UserFavorites::class)
|
||||||
|
) val users: List<User>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserWithFavorites(
|
||||||
|
@Embedded
|
||||||
|
val user: User,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "user_id",
|
||||||
|
entityColumn = "dish_id",
|
||||||
|
associateBy = Junction(UserFavorites::class)
|
||||||
|
) val favoriteDishes: List<Dish>
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
|
||||||
|
interface CategoryRepository {
|
||||||
|
suspend fun getAll(): List<Category>
|
||||||
|
suspend fun insert(category: Category)
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface DishRepository {
|
||||||
|
suspend fun getAllDishes(): List<Dish>
|
||||||
|
suspend fun getDish(uid: Int): Dish?
|
||||||
|
suspend fun getUserDishes(userUid: Int) : List<Dish>
|
||||||
|
suspend fun insertDish(dish: Dish)
|
||||||
|
suspend fun updateDish(dish: Dish)
|
||||||
|
suspend fun deleteDish(dish: Dish)
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.dao.CategoryDao
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository {
|
||||||
|
override suspend fun getAll(): List<Category> {
|
||||||
|
return categoryDao.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insert(category: Category) = categoryDao.insert(category)
|
||||||
|
|
||||||
|
suspend fun deleteAll() = categoryDao.deleteAll()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.dao.DishDao
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class OfflineDishRepository(private val dishDao: DishDao) : DishRepository {
|
||||||
|
override suspend fun getAllDishes(): List<Dish> {
|
||||||
|
return dishDao.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDish(uid: Int): Dish? {
|
||||||
|
return dishDao.getByUid(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUserDishes(userUid: Int): List<Dish> {
|
||||||
|
return dishDao.getAllOFUser(userUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertDish(dish: Dish) {
|
||||||
|
dishDao.insert(dish)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateDish(dish: Dish) {
|
||||||
|
dishDao.update(dish)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteDish(dish: Dish) {
|
||||||
|
dishDao.delete(dish)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.dao.UserFavoritesDao
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.UserFavorites
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class OfflineUserFavoritesRepository(private val userFavoritesDao: UserFavoritesDao) : UserWithFavoritesRepository {
|
||||||
|
override suspend fun getUserFavorites(userUid: Int): List<Dish> {
|
||||||
|
return userFavoritesDao.getUserFavorites(userUid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites? {
|
||||||
|
return userFavoritesDao.getUserFavorite(userUid, dishUid)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun deleteUserFavorites(userUid: Int, dishUid: Int) {
|
||||||
|
userFavoritesDao.delete(UserFavorites(userUid, dishUid))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUserFavorites(userUid: Int, dishUid: Int) {
|
||||||
|
userFavoritesDao.insert(UserFavorites(userUid, dishUid))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.dao.UserDao
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
|
||||||
|
override suspend fun getAllUsers(): List<User> {
|
||||||
|
return userDao.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUser(uid: Int): User? {
|
||||||
|
return userDao.getByUid(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUser(user: User) {
|
||||||
|
userDao.insert(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateUser(user: User) {
|
||||||
|
userDao.update(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteUser(user: User) {
|
||||||
|
userDao.delete(user)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface UserRepository {
|
||||||
|
suspend fun getAllUsers(): List<User>
|
||||||
|
suspend fun getUser(uid: Int): User?
|
||||||
|
suspend fun insertUser(user: User)
|
||||||
|
suspend fun updateUser(user: User)
|
||||||
|
suspend fun deleteUser(user: User)
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.example.myapplication.database.repository
|
||||||
|
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.UserFavorites
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface UserWithFavoritesRepository {
|
||||||
|
suspend fun getUserFavorites(userUid: Int): List<Dish>
|
||||||
|
|
||||||
|
suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites?
|
||||||
|
|
||||||
|
suspend fun deleteUserFavorites(userUid: Int, dishUid: Int)
|
||||||
|
|
||||||
|
suspend fun insertUserFavorites(userUid: Int, dishUid: Int)
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.example.myapplication.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
|
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||||
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import com.example.myapplication.ui.navigation.Screen
|
||||||
|
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Navbar(
|
||||||
|
navController: NavHostController?,
|
||||||
|
currentDestination: NavDestination?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
NavigationBar(modifier, containerColor = MaterialTheme.colorScheme.primary) {
|
||||||
|
Screen.navbarItems.forEach {screen ->
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = currentDestination?.hierarchy?.any {it.route == screen.route} == true,
|
||||||
|
icon = {
|
||||||
|
if (screen.icon != null) {
|
||||||
|
Icon(screen.icon, contentDescription = null, modifier = Modifier.width(50.dp).height(50.dp))
|
||||||
|
}
|
||||||
|
else if (screen.icon_id != null){
|
||||||
|
Icon(painter = painterResource(id = screen.icon_id), contentDescription = null, modifier = Modifier.width(50.dp).height(50.dp))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate(screen.route) {
|
||||||
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
|
} },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name="Navbar")
|
||||||
|
@Composable
|
||||||
|
fun NavBarPreview() {
|
||||||
|
MyApplicationTheme {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
) {
|
||||||
|
Navbar(navController = null, currentDestination = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
package com.example.myapplication.ui.dishes.list
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
|
import androidx.compose.material.icons.filled.FavoriteBorder
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.AppViewModelProvider
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.R
|
||||||
|
import com.example.myapplication.database.PrefRepository
|
||||||
|
import com.example.myapplication.ui.extra.ErrorElement
|
||||||
|
import com.example.myapplication.ui.extra.ErrorsType
|
||||||
|
import com.example.myapplication.ui.navigation.Screen
|
||||||
|
import com.example.myapplication.ui.theme.textFont
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Composable
|
||||||
|
fun DishList(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: DishListViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
typeDishList: TypeDishList = TypeDishList.AllDishes,
|
||||||
|
userUid: Int?
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val dishListUiState by viewModel.dishListUiState.collectAsState()
|
||||||
|
val dishFavoritesUiState by viewModel.favoritesDishListUiState.collectAsState()
|
||||||
|
val dishUserUiState by viewModel.userDishListUiState.collectAsState()
|
||||||
|
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
) { innerPadding ->
|
||||||
|
if (typeDishList != TypeDishList.AllDishes && userUid == null) {
|
||||||
|
ErrorElement(navController = navController, typeErrorsType = ErrorsType.NOT_AUTH)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DishList(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(ScrollState(0)),
|
||||||
|
typeDishList = typeDishList,
|
||||||
|
dishListTytle = when (typeDishList) {
|
||||||
|
TypeDishList.AllDishes -> dishListUiState.dishList
|
||||||
|
TypeDishList.FavoritesDishes -> dishFavoritesUiState.dishList
|
||||||
|
TypeDishList.UserDishes -> dishUserUiState.dishList
|
||||||
|
else -> dishListUiState.dishList
|
||||||
|
},
|
||||||
|
favorites = dishFavoritesUiState.dishList,
|
||||||
|
addToFavorites = {uid: Int ->
|
||||||
|
if (userUid != null) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.addFavoritesToUser(userUid, uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {uid: Int ->
|
||||||
|
val route = Screen.DishView.route.replace("{id}", uid.toString())
|
||||||
|
navController.navigate(route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Composable
|
||||||
|
private fun DishList(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
typeDishList: TypeDishList,
|
||||||
|
dishListTytle: List<Dish>,
|
||||||
|
favorites: List<Dish>,
|
||||||
|
addToFavorites: (uid : Int) -> Unit,
|
||||||
|
onClick: (uid : Int) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column {
|
||||||
|
val text: String = when (typeDishList) {
|
||||||
|
TypeDishList.AllDishes -> stringResource(R.string.all_dishes)
|
||||||
|
TypeDishList.FavoritesDishes -> stringResource(id = R.string.favorite_dishes)
|
||||||
|
TypeDishList.UserDishes -> stringResource(id = R.string.user_dishes)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text, fontFamily= textFont,
|
||||||
|
fontSize=26.sp, textAlign = TextAlign.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dishListTytle.forEachIndexed() { index, dish ->
|
||||||
|
DishListItem(index=index, dish = dish, favorites = favorites, addToFavorites, onClick = onClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Composable
|
||||||
|
private fun DishListItem(
|
||||||
|
index: Int,
|
||||||
|
dish: Dish,
|
||||||
|
favorites: List<Dish>,
|
||||||
|
addToFavorites: (uid : Int) -> Unit,
|
||||||
|
onClick: (uid: Int) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(vertical = 5.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
.clickable {
|
||||||
|
onClick(dish.uid!!)
|
||||||
|
}) {
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
// if (dish.image != null) { // TODO Image input check
|
||||||
|
if (false) {
|
||||||
|
Image(
|
||||||
|
bitmap = dish.getBitmapFromByteArray()!!.asImageBitmap(),
|
||||||
|
contentDescription = "Dish Image",
|
||||||
|
Modifier.width(150.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Image(
|
||||||
|
Icons.Filled.Warning,
|
||||||
|
contentDescription = "Dish Image",
|
||||||
|
Modifier.width(150.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
dish.name, fontFamily = textFont,
|
||||||
|
fontSize = 15.sp, textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
dish.description, fontFamily = textFont,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
softWrap = true,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
Modifier.clickable { addToFavorites(dish.uid!!) }
|
||||||
|
) {
|
||||||
|
if (favorites.contains(dish)) {
|
||||||
|
Icon(Icons.Filled.Favorite, contentDescription = "")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Icon(Icons.Filled.FavoriteBorder, contentDescription = "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ListDishes() {
|
||||||
|
// MyApplicationTheme {
|
||||||
|
// Surface(
|
||||||
|
// color = MaterialTheme.colorScheme.background
|
||||||
|
// ) {
|
||||||
|
// DishList(navController = null, onlyFavorites = false)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.example.myapplication.ui.dishes.list
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.myapplication.database.AppDataContainer
|
||||||
|
import com.example.myapplication.database.PrefRepository
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.repository.DishRepository
|
||||||
|
import com.example.myapplication.database.repository.UserWithFavoritesRepository
|
||||||
|
import com.example.myapplication.ui.user.UserUiState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
enum class TypeDishList {
|
||||||
|
AllDishes,
|
||||||
|
FavoritesDishes,
|
||||||
|
UserDishes
|
||||||
|
}
|
||||||
|
class DishListViewModel(
|
||||||
|
private val dishRepository: DishRepository,
|
||||||
|
private val userWithFavoritesRepository: UserWithFavoritesRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _dishListUiState = MutableStateFlow(DishListUiState())
|
||||||
|
val dishListUiState: StateFlow<DishListUiState> = _dishListUiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _userDishListUiState = MutableStateFlow(DishListUiState())
|
||||||
|
val userDishListUiState: StateFlow<DishListUiState> = _dishListUiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _favoritesDishListUiState = MutableStateFlow(DishListUiState())
|
||||||
|
val favoritesDishListUiState: StateFlow<DishListUiState> = _dishListUiState.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_dishListUiState.update { currentScope ->
|
||||||
|
currentScope.copy(dishRepository.getAllDishes())
|
||||||
|
}
|
||||||
|
_favoritesDishListUiState.update { currentScope ->
|
||||||
|
currentScope.copy(userWithFavoritesRepository.getUserFavorites(2))
|
||||||
|
}
|
||||||
|
_userDishListUiState.update { currentScope ->
|
||||||
|
currentScope.copy(dishRepository.getUserDishes(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun addFavoritesToUser(userId: Int, dishId: Int) {
|
||||||
|
val userFavorite = userWithFavoritesRepository.getUserFavorite(userId, dishId)
|
||||||
|
if (userFavorite == null) {
|
||||||
|
userWithFavoritesRepository.insertUserFavorites(userId, dishId)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
userWithFavoritesRepository.deleteUserFavorites(userId, dishId)
|
||||||
|
}
|
||||||
|
_favoritesDishListUiState.update { currentScope ->
|
||||||
|
currentScope.copy(userWithFavoritesRepository.getUserFavorites(userId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class DishListUiState(val dishList: List<Dish> = listOf())
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.example.myapplication.ui.dishes.view
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import com.example.myapplication.database.repository.CategoryRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CategoryDropDownViewModel(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
var categoryListUiState by mutableStateOf(CategoryListUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var categoryUiState by mutableStateOf(CategoryUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
categoryListUiState = CategoryListUiState(categoryRepository.getAll())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentCategory(categoryId: Int) {
|
||||||
|
val category: Category? =
|
||||||
|
categoryListUiState.categoryList.firstOrNull { category -> category.uid == categoryId }
|
||||||
|
category?.let { updateUiState(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateUiState(category: Category) {
|
||||||
|
categoryUiState = CategoryUiState(
|
||||||
|
category = category
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CategoryListUiState(val categoryList: List<Category> = listOf())
|
||||||
|
|
||||||
|
data class CategoryUiState(
|
||||||
|
val category: Category? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Category.toUiState() = CategoryUiState(category = Category(uid = uid, name = name))
|
@ -0,0 +1,156 @@
|
|||||||
|
package com.example.myapplication.ui.dishes.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.AppViewModelProvider
|
||||||
|
import com.example.myapplication.R
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun DishEditView (
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: DishEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
categoryViewModel: CategoryDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
userUid: Int
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
categoryViewModel.setCurrentCategory(viewModel.dishEditUiState.dishDetails.categoryUid!!)
|
||||||
|
viewModel.setCurrentUser(userUid)
|
||||||
|
DishEditView(
|
||||||
|
dishUiState = viewModel.dishEditUiState,
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.saveDish()
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUpdate = viewModel::updateUiState,
|
||||||
|
categoryUiState = categoryViewModel.categoryUiState,
|
||||||
|
categoryListUiState = categoryViewModel.categoryListUiState,
|
||||||
|
onCategoryUpdate = categoryViewModel::updateUiState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun CategoryDropDown(
|
||||||
|
categoryUiState: CategoryUiState,
|
||||||
|
categoryListUiState: CategoryListUiState,
|
||||||
|
onCategoryUpdate: (Category) -> Unit
|
||||||
|
) {
|
||||||
|
var expanded: Boolean by remember { mutableStateOf(false) }
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 7.dp),
|
||||||
|
expanded = expanded,
|
||||||
|
onExpandedChange = {
|
||||||
|
expanded = !expanded
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = categoryUiState.category?.name
|
||||||
|
?: stringResource(id = R.string.category_not_found),
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
trailingIcon = {
|
||||||
|
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.menuAnchor()
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.background(Color.White)
|
||||||
|
.exposedDropdownSize()
|
||||||
|
) {
|
||||||
|
categoryListUiState.categoryList.forEach { category ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = category.name)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onCategoryUpdate(category)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun DishEditView(
|
||||||
|
dishUiState: DishEditUiState,
|
||||||
|
categoryUiState: CategoryUiState,
|
||||||
|
categoryListUiState: CategoryListUiState,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onUpdate: (DishDetails) -> Unit,
|
||||||
|
onCategoryUpdate: (Category) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 10.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = dishUiState.dishDetails.name,
|
||||||
|
onValueChange = { onUpdate(dishUiState.dishDetails.copy(name = it)) },
|
||||||
|
label = { Text(stringResource(id = R.string.dish_label)) },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = dishUiState.dishDetails.description,
|
||||||
|
onValueChange = { onUpdate(dishUiState.dishDetails.copy(description = it)) },
|
||||||
|
label = { Text(stringResource(id = R.string.dish_description)) },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
CategoryDropDown(
|
||||||
|
categoryUiState = categoryUiState,
|
||||||
|
categoryListUiState = categoryListUiState,
|
||||||
|
onCategoryUpdate = {
|
||||||
|
onUpdate(dishUiState.dishDetails.copy(categoryUid = it.uid))
|
||||||
|
onCategoryUpdate(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = dishUiState.isEntryValid,
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.dish_save))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package com.example.myapplication.ui.dishes.view
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.myapplication.database.model.Category
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.database.repository.DishRepository
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class DishEditViewModel (
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val dishRepository: DishRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
var dishEditUiState by mutableStateOf(DishEditUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val dishUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
|
||||||
|
// init {
|
||||||
|
// viewModelScope.launch {
|
||||||
|
// if (dishUid > 0) {
|
||||||
|
// dishEditUiState = dishRepository.getDish(dishUid)
|
||||||
|
// .filterNotNull()
|
||||||
|
// .first().dish!!
|
||||||
|
// .toUiState(true)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
fun updateUiState(dishDetails: DishDetails) {
|
||||||
|
dishEditUiState = DishEditUiState(
|
||||||
|
dishDetails = dishDetails,
|
||||||
|
isEntryValid = validateInput(dishDetails)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentUser(userId: Int) {
|
||||||
|
val dish: DishDetails? =
|
||||||
|
dishEditUiState.dishDetails.copy(userUid = userId)
|
||||||
|
dish?.let { updateUiState(it) }
|
||||||
|
}
|
||||||
|
suspend fun saveDish() {
|
||||||
|
if (validateInput()) {
|
||||||
|
if (dishUid > 0) {
|
||||||
|
dishRepository.updateDish(dishEditUiState.dishDetails.toDish(dishUid))
|
||||||
|
} else {
|
||||||
|
// dishEditUiState.dishDetails.copy(uid)
|
||||||
|
dishRepository.insertDish(dishEditUiState.dishDetails.toDish())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateInput(uiState: DishDetails = dishEditUiState.dishDetails): Boolean {
|
||||||
|
return with(uiState) {
|
||||||
|
name.isNotBlank()
|
||||||
|
&& description.isNotBlank()
|
||||||
|
&& userUid!! > 0
|
||||||
|
&& categoryUid!! > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DishEditUiState(
|
||||||
|
val dishDetails: DishDetails = DishDetails(),
|
||||||
|
val isEntryValid: Boolean = false
|
||||||
|
)
|
||||||
|
data class DishDetails(
|
||||||
|
val name: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val image: ByteArray? = null,
|
||||||
|
val userUid: Int? = 0,
|
||||||
|
val categoryUid: Int? = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
fun DishDetails.toDish(uid: Int? = null): Dish = Dish(
|
||||||
|
uid = uid,
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
image = image,
|
||||||
|
userId = userUid,
|
||||||
|
categoryId = categoryUid
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Dish.toDetails(): DishDetails = DishDetails(
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
image = image,
|
||||||
|
userUid = userId,
|
||||||
|
categoryUid = categoryId
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Dish.toUiState(isEntryValid: Boolean = false): DishEditUiState = DishEditUiState(
|
||||||
|
dishDetails = this.toDetails(),
|
||||||
|
isEntryValid = isEntryValid
|
||||||
|
)
|
@ -0,0 +1,146 @@
|
|||||||
|
package com.example.myapplication.ui.dishes
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.AppViewModelProvider
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import com.example.myapplication.R
|
||||||
|
import com.example.myapplication.database.AppDatabase
|
||||||
|
import com.example.myapplication.ui.dishes.view.DishViewModel
|
||||||
|
import com.example.myapplication.ui.extra.ErrorElement
|
||||||
|
import com.example.myapplication.ui.extra.ErrorsType
|
||||||
|
import com.example.myapplication.ui.navigation.Screen
|
||||||
|
//import com.example.myapplication.Dishes.Model.getAllDishes
|
||||||
|
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||||
|
import com.example.myapplication.ui.theme.textFont
|
||||||
|
import com.example.myapplication.ui.user.UserViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun DishView(navController: NavController,
|
||||||
|
viewModel: DishViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
userUid: Int?,
|
||||||
|
dishUid: Int?) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
// val dishUiState by viewModel.getDish(dishUid ?: 0).collectAsState()
|
||||||
|
// Scaffold(
|
||||||
|
// floatingActionButton = {
|
||||||
|
// if (dishUiState.dish?.dish?.userId == userUid) {
|
||||||
|
// FloatingActionButton(
|
||||||
|
// onClick = {
|
||||||
|
// val route = Screen.DishEdit.route.replace("{id}", dishUid.toString())
|
||||||
|
// navController.navigate(route)
|
||||||
|
// },
|
||||||
|
// ) {
|
||||||
|
// Icon(Icons.Filled.Add, "Добавить")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ){ innerPadding ->
|
||||||
|
// if (dishUiState.dish == null) {
|
||||||
|
// ErrorElement(navController = navController, typeErrorsType = ErrorsType.NOT_FOUND)
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// DishView(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .padding(innerPadding)
|
||||||
|
// .fillMaxSize(),
|
||||||
|
// dish = dishUiState.dish!!
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Composable
|
||||||
|
private fun DishView(
|
||||||
|
modifier: Modifier,
|
||||||
|
dish : DishWithCategoryAndUser
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 5.dp, horizontal = 10.dp)
|
||||||
|
.verticalScroll(ScrollState(0))) {
|
||||||
|
Row(Modifier.padding(vertical = 5.dp)) {
|
||||||
|
if (dish.dish?.image != null) {
|
||||||
|
Image(
|
||||||
|
bitmap = dish.dish.getBitmapFromByteArray()!!.asImageBitmap(),
|
||||||
|
contentDescription = "Dish Image",
|
||||||
|
Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Image(
|
||||||
|
painterResource(id = R.drawable.sushi),
|
||||||
|
contentDescription = "Dish Image",
|
||||||
|
Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column {
|
||||||
|
Text(dish.dish!!.name, fontFamily=textFont,
|
||||||
|
fontSize=15.sp, textAlign = TextAlign.Start)
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Text("@" + dish.nickname, fontFamily=textFont,
|
||||||
|
fontSize=15.sp, textAlign = TextAlign.End)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
horizontal = 30.dp, vertical = 10.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
dish.dish!!.description,
|
||||||
|
fontFamily=textFont,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
softWrap = true,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.ui.dishes.view
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.myapplication.database.AppDataContainer
|
||||||
|
import com.example.myapplication.database.model.DishWithCategoryAndUser
|
||||||
|
import com.example.myapplication.database.repository.DishRepository
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
class DishViewModel(
|
||||||
|
private val dishRepository: DishRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
// fun getDish(uid: Int) : StateFlow<DishViewUiState> {
|
||||||
|
// return dishRepository.getDish(uid).map {
|
||||||
|
// DishViewUiState(it!!)
|
||||||
|
// }.stateIn(
|
||||||
|
// scope = viewModelScope,
|
||||||
|
// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
||||||
|
// initialValue = DishViewUiState()
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DishViewUiState(val dish: DishWithCategoryAndUser? = null)
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.example.myapplication.ui.extra
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.AppViewModelProvider
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishList
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishListViewModel
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ErrorElement(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: ErrorsViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
typeErrorsType: ErrorsType
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
) { innerPadding ->
|
||||||
|
ErrorElement(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
text = viewModel.getError(typeErrorsType).errorText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ErrorElement(
|
||||||
|
modifier: Modifier,
|
||||||
|
text: String
|
||||||
|
) {
|
||||||
|
Text(text = text)
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.myapplication.ui.extra
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.myapplication.database.AppDataContainer
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishListUiState
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
enum class ErrorsType {
|
||||||
|
NOT_AUTH,
|
||||||
|
NOT_FOUND
|
||||||
|
}
|
||||||
|
class ErrorsViewModel : ViewModel() {
|
||||||
|
fun getError(type: ErrorsType) : ErrorUiState {
|
||||||
|
return when (type) {
|
||||||
|
ErrorsType.NOT_AUTH -> ErrorUiState("Auth first")
|
||||||
|
ErrorsType.NOT_FOUND -> ErrorUiState("Not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class ErrorUiState(val errorText: String = "")
|
@ -0,0 +1,97 @@
|
|||||||
|
package com.example.myapplication.ui.navigation
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishList
|
||||||
|
//import com.example.myapplication.ui.dishes.DishView
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
//import com.example.myapplication.ui.user.UserView
|
||||||
|
import com.example.myapplication.ui.components.Navbar
|
||||||
|
import com.example.myapplication.ui.dishes.DishView
|
||||||
|
import com.example.myapplication.ui.dishes.list.TypeDishList
|
||||||
|
import com.example.myapplication.ui.dishes.view.DishEditView
|
||||||
|
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||||
|
import com.example.myapplication.ui.user.UserView
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Composable
|
||||||
|
fun Navhost(
|
||||||
|
navController: NavHostController,
|
||||||
|
innerPadding: PaddingValues, modifier:
|
||||||
|
Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val userUid = 2;
|
||||||
|
NavHost(
|
||||||
|
navController,
|
||||||
|
startDestination = Screen.AllDishes.route,
|
||||||
|
modifier.padding(innerPadding)
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
composable(Screen.AllDishes.route) { DishList(navController, userUid = userUid) }
|
||||||
|
composable(Screen.FavoriteDishes.route) { DishList(navController, userUid = userUid, typeDishList = TypeDishList.FavoritesDishes) }
|
||||||
|
composable(Screen.UserPage.route) { UserView(navController, userUid = userUid) }
|
||||||
|
|
||||||
|
composable(Screen.DishView.route,
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
|
) { backStackEntry ->
|
||||||
|
backStackEntry.arguments?.let { DishView(navController, userUid = userUid, dishUid = it.getInt("id")) }
|
||||||
|
}
|
||||||
|
composable(Screen.DishEdit.route,
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
|
) {
|
||||||
|
DishEditView(navController = navController, userUid=userUid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun MainNavbar() {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentDestination = navBackStackEntry?.destination
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
Navbar(navController, currentDestination, Modifier.background(MaterialTheme.colorScheme.primary))
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
Navhost(navController, innerPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Preview(name = "Dark Mode")
|
||||||
|
@Composable
|
||||||
|
fun MainNavbarPreview() {
|
||||||
|
MyApplicationTheme {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
MainNavbar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.example.myapplication.ui.navigation
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.AccountCircle
|
||||||
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import com.example.myapplication.R
|
||||||
|
|
||||||
|
enum class Screen(
|
||||||
|
val route: String,
|
||||||
|
@StringRes val resourceId: Int,
|
||||||
|
val icon: ImageVector? = null,
|
||||||
|
val icon_id: Int? = null,
|
||||||
|
|
||||||
|
) {
|
||||||
|
AllDishes("all-dishes", R.string.all_dishes, icon_id = R.drawable.cooking_book),
|
||||||
|
DishView("dish/{id}", R.string.all_dishes, icon=Icons.Filled.Info),
|
||||||
|
FavoriteDishes("favorite-dishes", R.string.favorite_dishes, icon=Icons.Filled.Favorite),
|
||||||
|
UserPage("user", R.string.account_title, icon=Icons.Filled.AccountCircle),
|
||||||
|
DishEdit("dish-edit/{id}", R.string.all_dishes);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val navbarItems = listOf(
|
||||||
|
FavoriteDishes,
|
||||||
|
AllDishes,
|
||||||
|
UserPage,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getItem(route: String): Screen? {
|
||||||
|
val findRoute = route.split("/").first()
|
||||||
|
return values().find { value -> value.route.startsWith(findRoute) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,6 @@ package com.example.myapplication.ui.theme
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
val GreyBackground = Color(0xFFE9DFDF);
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val SkinExtra = Color(0xFFE6CF94)
|
||||||
val Pink40 = Color(0xFF7D5260)
|
|
13
app/src/main/java/com/example/myapplication/ui/theme/Font.kt
Normal file
13
app/src/main/java/com/example/myapplication/ui/theme/Font.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.example.myapplication.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import com.example.myapplication.R
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
val textFont = FontFamily(
|
||||||
|
Font(R.font.header1, FontWeight.Light),
|
||||||
|
)
|
@ -10,21 +10,15 @@ import androidx.compose.material3.dynamicLightColorScheme
|
|||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
|
||||||
primary = Purple80,
|
|
||||||
secondary = PurpleGrey80,
|
|
||||||
tertiary = Pink80
|
|
||||||
)
|
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = Purple40,
|
background = GreyBackground,
|
||||||
secondary = PurpleGrey40,
|
primary = SkinExtra
|
||||||
tertiary = Pink40
|
|
||||||
|
|
||||||
/* Other default colors to override
|
/* Other default colors to override
|
||||||
background = Color(0xFFFFFBFE),
|
background = Color(0xFFFFFBFE),
|
||||||
@ -44,26 +38,9 @@ fun MyApplicationTheme(
|
|||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = true,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = when {
|
|
||||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
||||||
val context = LocalContext.current
|
|
||||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
darkTheme -> DarkColorScheme
|
|
||||||
else -> LightColorScheme
|
|
||||||
}
|
|
||||||
val view = LocalView.current
|
|
||||||
if (!view.isInEditMode) {
|
|
||||||
SideEffect {
|
|
||||||
val window = (view.context as Activity).window
|
|
||||||
window.statusBarColor = colorScheme.primary.toArgb()
|
|
||||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = LightColorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
|
106
app/src/main/java/com/example/myapplication/ui/user/UserView.kt
Normal file
106
app/src/main/java/com/example/myapplication/ui/user/UserView.kt
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package com.example.myapplication.ui.user
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.example.myapplication.AppViewModelProvider
|
||||||
|
//import com.example.myapplication.Dishes.Model.getAllDishes
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishList
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.ui.dishes.list.TypeDishList
|
||||||
|
import com.example.myapplication.ui.extra.ErrorElement
|
||||||
|
import com.example.myapplication.ui.extra.ErrorsType
|
||||||
|
import com.example.myapplication.ui.navigation.Screen
|
||||||
|
//import com.example.myapplication.User.Model.getAllUsers
|
||||||
|
import com.example.myapplication.ui.theme.textFont
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.prefs.Preferences
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun UserView (
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
|
userUid: Int?
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val userUiState by viewModel.userState.collectAsState()
|
||||||
|
|
||||||
|
coroutineScope.let {
|
||||||
|
it.launch { viewModel.getUser(userUid ?: 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
val route = Screen.DishEdit.route.replace("{id}", 0.toString())
|
||||||
|
navController.navigate(route)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.Add, "Добавить")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {innerPadding ->
|
||||||
|
if (userUiState.user == null) {
|
||||||
|
ErrorElement(navController = navController, typeErrorsType = ErrorsType.NOT_AUTH)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
UserView(
|
||||||
|
navController = navController,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
user = userUiState.user!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@Composable
|
||||||
|
private fun UserView(
|
||||||
|
navController: NavController,
|
||||||
|
modifier: Modifier,
|
||||||
|
user: User
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 5.dp, horizontal = 10.dp)) {
|
||||||
|
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column {
|
||||||
|
Text(user.nickname, fontFamily= textFont,
|
||||||
|
fontSize=26.sp, textAlign = TextAlign.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
|
DishList(navController, userUid = user.uid, typeDishList = TypeDishList.UserDishes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.example.myapplication.ui.user
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.myapplication.database.AppDataContainer
|
||||||
|
import com.example.myapplication.database.model.Dish
|
||||||
|
import com.example.myapplication.database.model.User
|
||||||
|
import com.example.myapplication.database.repository.DishRepository
|
||||||
|
import com.example.myapplication.database.repository.UserRepository
|
||||||
|
import com.example.myapplication.database.repository.UserWithFavoritesRepository
|
||||||
|
import com.example.myapplication.ui.dishes.list.DishListUiState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
class UserViewModel(
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _userState = MutableStateFlow(UserUiState())
|
||||||
|
val userState: StateFlow<UserUiState> = _userState.asStateFlow()
|
||||||
|
suspend fun getUser(uid: Int) {
|
||||||
|
_userState.update { currentState ->
|
||||||
|
currentState.copy(userRepository.getUser(uid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UserUiState(val user: User? = null)
|
9
app/src/main/res/drawable/cooking_book.xml
Normal file
9
app/src/main/res/drawable/cooking_book.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="800dp"
|
||||||
|
android:height="800dp"
|
||||||
|
android:viewportWidth="50"
|
||||||
|
android:viewportHeight="50">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,39h32V2H12C8.691,2 6,4.691 6,8v34.417C6,45.496 8.691,48 12,48h32v-2H12c-2.168,0 -4,-1.641 -4,-3.583C8,40.501 9.757,39 12,39zM36.709,31.706C36.514,31.902 36.257,32 36,32c-0.255,0 -0.511,-0.097 -0.705,-0.292l-6.523,-6.494l-1.76,1.76l-1.846,-1.879l3.153,-3.153l8.387,8.349C37.097,30.681 37.099,31.314 36.709,31.706zM16.286,10.007l7.733,7.781l-3.044,3.044L16.23,16C14.568,14.338 14.594,11.637 16.286,10.007zM14.329,30.293l13.024,-13.024c-0.034,-0.085 -0.083,-0.163 -0.107,-0.252c-0.399,-1.509 -0.322,-3.426 1.045,-4.777c2.031,-2.094 5.497,-2.989 6.998,-1.505c1.501,1.571 0.596,4.909 -1.435,6.916c-1.444,1.428 -3.298,1.545 -4.8,1.16c-0.104,-0.027 -0.196,-0.081 -0.294,-0.122L14.743,31.707C14.548,31.902 15.292,32 15.036,32s-0.512,-0.098 -0.707,-0.293C13.938,31.316 13.938,30.684 14.329,30.293z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
BIN
app/src/main/res/drawable/pepperoni.png
Normal file
BIN
app/src/main/res/drawable/pepperoni.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
BIN
app/src/main/res/drawable/sushi.png
Normal file
BIN
app/src/main/res/drawable/sushi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
BIN
app/src/main/res/font/header1.ttf
Normal file
BIN
app/src/main/res/font/header1.ttf
Normal file
Binary file not shown.
@ -1,3 +1,18 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">My Application</string>
|
<string name="app_name">Food Warriors</string>
|
||||||
|
<string name="account_title">Account</string>
|
||||||
|
<string name="all_dishes">All Dishes</string>
|
||||||
|
<string name="favorite_dishes">Favorite Dishes</string>
|
||||||
|
<string name="user_dishes">User Dishes</string>
|
||||||
|
<string name="lorem_ipsum">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam risus orci, ultrices at risus pulvinar, volutpat dapibus ex. Pellentesque vel efficitur libero. Vestibulum consequat, mauris nec vehicula tempus, risus libero dignissim purus, quis porttitor risus metus et erat. Maecenas cursus justo a augue euismod, sed faucibus tortor consectetur. Praesent vitae feugiat ipsum. Sed nulla lacus, varius sit amet libero vitae, ornare auctor enim. Proin molestie arcu sapien, eget accumsan magna dignissim sed. Sed elementum vel nisi vitae sollicitudin. Suspendisse sollicitudin fermentum leo quis fermentum. Aenean semper sem leo, in scelerisque mi eleifend vitae.
|
||||||
|
Quisque efficitur, nunc vitae varius faucibus, turpis nunc ullamcorper neque, in semper nibh purus ac elit. Nulla dapibus sed sem eu fermentum. Mauris imperdiet sapien ut laoreet cursus. Fusce finibus augue et risus dignissim vehicula. Ut at mi fermentum, posuere nulla ornare, mollis neque. Quisque mattis accumsan odio at commodo. Nullam accumsan fermentum rhoncus. Donec ut sapien velit. Fusce id vulputate augue. Quisque faucibus dolor id leo feugiat feugiat.
|
||||||
|
Donec molestie scelerisque sollicitudin. Duis orci felis, lacinia laoreet scelerisque sed, molestie sed ipsum. Duis auctor aliquet laoreet. Mauris vitae commodo nibh. Sed pulvinar metus lacus, a faucibus nulla ornare ultricies. Aliquam varius sem a nibh dapibus congue. Phasellus dignissim nibh diam. Etiam elit nulla, bibendum ac quam sed, dictum condimentum ligula.
|
||||||
|
Phasellus eget ex sed tortor placerat suscipit quis quis magna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean mi quam, auctor quis ante nec, condimentum maximus justo. Morbi varius est massa. Integer nec semper mi. Fusce nisl neque, mollis quis tempus eget, vulputate vitae sem. Donec sit amet maximus augue. Quisque sit amet ante fermentum, bibendum mi ut, porta eros. Phasellus ultrices, augue a fringilla accumsan, elit arcu vehicula lectus, condimentum malesuada lectus enim nec tellus. Nunc ante erat, convallis a dui vitae, rhoncus rhoncus metus. Suspendisse in sapien odio.
|
||||||
|
Cras semper magna id libero gravida, at varius elit molestie. Quisque ullamcorper nisi tincidunt, feugiat justo nec, aliquam massa. Quisque sit amet quam semper, pretium justo non, tincidunt lorem. Proin rhoncus turpis orci, efficitur dignissim ex sollicitudin eget. Etiam porttitor est eget erat pellentesque, ut maximus mi dictum. Pellentesque ac mauris non eros mollis pharetra et eu augue. Nam a bibendum ipsum, vel pellentesque nisi. Nam gravida dolor sed risus ornare dignissim. Praesent semper, tellus sed posuere efficitur, neque justo efficitur dui, et mollis leo ex in quam. Fusce pulvinar turpis a justo pretium semper. Integer dapibus, risus vel facilisis porttitor, eros sapien venenatis risus, eget aliquet lectus ipsum a orci. Ut at arcu a augue varius gravida non sit amet quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam eget enim eu mauris rutrum pulvinar.
|
||||||
|
</string>
|
||||||
|
<string name="dish_label">Name of dish</string>
|
||||||
|
<string name="dish_description">Description</string>
|
||||||
|
<string name="dish_save">Save</string>
|
||||||
|
<string name="category_not_found">Choose category</string>
|
||||||
</resources>
|
</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">10.0.2.2</domain>
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
@ -2,4 +2,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.1.1" apply false
|
id("com.android.application") version "8.1.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
||||||
|
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
|
||||||
|
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply false
|
||||||
}
|
}
|
34
server/app.py
Normal file
34
server/app.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from flask import Flask, url_for, render_template, jsonify, make_response, abort, redirect, request
|
||||||
|
from flask_login import LoginManager, login_user, login_required, logout_user, current_user
|
||||||
|
from flask_restful import Api
|
||||||
|
|
||||||
|
from recources import db_session, UserFavoritesResource
|
||||||
|
from recources.CategoryResource import CategoryListResource, CategoryResource
|
||||||
|
from recources.DishResource import DishListResource, DishResource, DishUserListResource
|
||||||
|
from recources.UserFavoritesResource import UsersFavoritesListResource, UserFavoriteResource
|
||||||
|
from recources.UserResource import UsersListResource, UserResource
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = 'my_project_key'
|
||||||
|
db_session.global_init('dishWarriors.db')
|
||||||
|
api = Api(app)
|
||||||
|
api.prefix = "/api"
|
||||||
|
|
||||||
|
api.add_resource(UsersListResource, '/users')
|
||||||
|
api.add_resource(UserResource, "/user/<int:user_id>")
|
||||||
|
api.add_resource(CategoryListResource, '/categories')
|
||||||
|
api.add_resource(CategoryResource, "/category/<int:category_id>")
|
||||||
|
api.add_resource(DishListResource, '/dishes')
|
||||||
|
api.add_resource(DishResource, "/dish/<int:dish_id>")
|
||||||
|
api.add_resource(DishUserListResource, "/user_dish/<int:user_id>")
|
||||||
|
api.add_resource(UserFavoriteResource, "/favorite/<int:user_id>/<int:dish_id>")
|
||||||
|
api.add_resource(UsersFavoritesListResource, "/favorites/<int:user_id>")
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def not_found(error):
|
||||||
|
return make_response(jsonify({'error': 'Not found'})), 404
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(port=8083, host='127.0.0.1')
|
BIN
server/dishWarriors.db
Normal file
BIN
server/dishWarriors.db
Normal file
Binary file not shown.
64
server/recources/CategoryResource.py
Normal file
64
server/recources/CategoryResource.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from flask_restful import reqparse, abort, Resource, marshal_with, fields
|
||||||
|
from . import db_session
|
||||||
|
from recources.Model.Category import Category
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
|
||||||
|
def abort_if_user_not_found(category_id):
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
user = db_sess.query(Category).get(category_id)
|
||||||
|
if not user:
|
||||||
|
abort(404, message=f'Category {category_id} not found')
|
||||||
|
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument('name', required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryResource(Resource):
|
||||||
|
def get(self, category_id):
|
||||||
|
abort_if_user_not_found(category_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
category = db_sess.query(Category).get(category_id)
|
||||||
|
return jsonify(
|
||||||
|
{'category': category.to_dict()}
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, category_id):
|
||||||
|
abort_if_user_not_found(category_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
category = db_sess.query(Category).get(category_id)
|
||||||
|
db_sess.delete(category)
|
||||||
|
db_sess.commit()
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
def put(self, category_id):
|
||||||
|
abort_if_user_not_found(category_id)
|
||||||
|
args = parser.parse_args()
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
category = db_sess.query(Category).get(category_id)
|
||||||
|
category.name = args["name"]
|
||||||
|
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryListResource(Resource):
|
||||||
|
def get(self):
|
||||||
|
session = db_session.create_session()
|
||||||
|
categories = session.query(Category).all()
|
||||||
|
return jsonify([item.to_dict() for item in categories])
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
args = parser.parse_args()
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
category = Category()
|
||||||
|
category.name = args["name"]
|
||||||
|
|
||||||
|
db_sess.add(category)
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify({'success': 'OK'})
|
82
server/recources/DishResource.py
Normal file
82
server/recources/DishResource.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
from flask_restful import reqparse, abort, Resource
|
||||||
|
from . import db_session
|
||||||
|
from recources.Model.Dish import Dish
|
||||||
|
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
|
||||||
|
def abort_if_dish_not_found(dish_id):
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
user = db_sess.query(Dish).get(dish_id)
|
||||||
|
if not user:
|
||||||
|
abort(404, message=f'Dish {dish_id} not found')
|
||||||
|
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument('name', required=True)
|
||||||
|
parser.add_argument("description", required=True)
|
||||||
|
parser.add_argument("image", required=True)
|
||||||
|
parser.add_argument("userId", required=True)
|
||||||
|
parser.add_argument("categoryId", required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class DishResource(Resource):
|
||||||
|
def get(self, dish_id):
|
||||||
|
abort_if_dish_not_found(dish_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
dish = db_sess.query(Dish)
|
||||||
|
return jsonify(dish.to_dict())
|
||||||
|
|
||||||
|
def delete(self, dish_id):
|
||||||
|
abort_if_dish_not_found(dish_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
dish = db_sess.query(Dish).get(dish_id)
|
||||||
|
db_sess.delete(dish)
|
||||||
|
db_sess.commit()
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
def put(self, dish_id):
|
||||||
|
abort_if_dish_not_found(dish_id)
|
||||||
|
args = parser.parse_args()
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
dish = db_sess.query(Dish).get(dish_id)
|
||||||
|
dish.name = args["name"]
|
||||||
|
dish.description = args["description"]
|
||||||
|
dish.image = args["image"]
|
||||||
|
dish.userId = args["userId"]
|
||||||
|
dish.categoryId = args["categoryId"]
|
||||||
|
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
|
||||||
|
class DishListResource(Resource):
|
||||||
|
def get(self):
|
||||||
|
session = db_session.create_session()
|
||||||
|
categories = session.query(Dish).all()
|
||||||
|
return jsonify([item.to_dict() for item in categories])
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
args = parser.parse_args()
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
dish = Dish()
|
||||||
|
dish.name = args["name"]
|
||||||
|
dish.description = args["description"]
|
||||||
|
dish.image = args["image"].encode()
|
||||||
|
dish.userId = args["userId"]
|
||||||
|
dish.categoryId = args["categoryId"]
|
||||||
|
|
||||||
|
db_sess.add(dish)
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
|
||||||
|
class DishUserListResource(Resource):
|
||||||
|
def get(self, user_id):
|
||||||
|
session = db_session.create_session()
|
||||||
|
categories = session.query(Dish).where(Dish.userId == user_id)
|
||||||
|
return jsonify([item.to_dict() for item in categories])
|
11
server/recources/Model/Category.py
Normal file
11
server/recources/Model/Category.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import sqlalchemy
|
||||||
|
from recources.db_session import SqlAlchemyBase
|
||||||
|
from sqlalchemy_serializer import SerializerMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Category(SqlAlchemyBase, SerializerMixin):
|
||||||
|
__tablename__ = 'categories'
|
||||||
|
|
||||||
|
id = sqlalchemy.Column(sqlalchemy.Integer,
|
||||||
|
primary_key=True, autoincrement=True)
|
||||||
|
name = sqlalchemy.Column(sqlalchemy.String)
|
15
server/recources/Model/Dish.py
Normal file
15
server/recources/Model/Dish.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import sqlalchemy
|
||||||
|
from recources.db_session import SqlAlchemyBase
|
||||||
|
from sqlalchemy_serializer import SerializerMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Dish(SqlAlchemyBase, SerializerMixin):
|
||||||
|
__tablename__ = 'dishes'
|
||||||
|
|
||||||
|
id = sqlalchemy.Column(sqlalchemy.Integer,
|
||||||
|
primary_key=True, autoincrement=True)
|
||||||
|
name = sqlalchemy.Column(sqlalchemy.String)
|
||||||
|
description = sqlalchemy.Column(sqlalchemy.String)
|
||||||
|
image = sqlalchemy.Column(sqlalchemy.LargeBinary)
|
||||||
|
userId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"))
|
||||||
|
categoryId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("categories.id"))
|
21
server/recources/Model/User.py
Normal file
21
server/recources/Model/User.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import sqlalchemy
|
||||||
|
from recources.db_session import SqlAlchemyBase
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
from sqlalchemy_serializer import SerializerMixin
|
||||||
|
|
||||||
|
|
||||||
|
class User(SqlAlchemyBase, UserMixin, SerializerMixin):
|
||||||
|
__tablename__ = 'users'
|
||||||
|
|
||||||
|
id = sqlalchemy.Column(sqlalchemy.Integer,
|
||||||
|
primary_key=True, autoincrement=True)
|
||||||
|
nickname = sqlalchemy.Column(sqlalchemy.String)
|
||||||
|
email = sqlalchemy.Column(sqlalchemy.String, unique=True)
|
||||||
|
hashed_password = sqlalchemy.Column(sqlalchemy.String)
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self.hashed_password = generate_password_hash(password)
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return check_password_hash(self.hashed_password, password)
|
10
server/recources/Model/UserFavorites.py
Normal file
10
server/recources/Model/UserFavorites.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import sqlalchemy
|
||||||
|
from recources.db_session import SqlAlchemyBase
|
||||||
|
from sqlalchemy_serializer import SerializerMixin
|
||||||
|
|
||||||
|
|
||||||
|
class UserFavorites(SqlAlchemyBase, SerializerMixin):
|
||||||
|
__tablename__ = 'user_favorites'
|
||||||
|
|
||||||
|
userId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"), primary_key=True)
|
||||||
|
dishId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("dishes.id"), primary_key=True)
|
1
server/recources/Model/__init__.py
Normal file
1
server/recources/Model/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import Category, Dish, User, UserFavorites
|
60
server/recources/UserFavoritesResource.py
Normal file
60
server/recources/UserFavoritesResource.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from flask_restful import reqparse, abort, Resource, marshal_with, fields
|
||||||
|
from . import db_session
|
||||||
|
from recources.Model.UserFavorites import UserFavorites
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
from .Model.Dish import Dish
|
||||||
|
|
||||||
|
|
||||||
|
def abort_if_user_favorites_not_found(user_id, dish_id):
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
user = db_sess.query(UserFavorites).get({
|
||||||
|
"userId": user_id,
|
||||||
|
"dishId": dish_id
|
||||||
|
})
|
||||||
|
if not user:
|
||||||
|
abort(404, message=f'User favorites {user_id} not found')
|
||||||
|
|
||||||
|
|
||||||
|
class UserFavoriteResource(Resource):
|
||||||
|
def get(self, user_id, dish_id):
|
||||||
|
abort_if_user_favorites_not_found(user_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
users = db_sess.query(UserFavorites).get({
|
||||||
|
"userId": user_id,
|
||||||
|
"dishId": dish_id
|
||||||
|
})
|
||||||
|
return jsonify(
|
||||||
|
{'users_favorite': users.to_dict()}
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, user_id, dish_id):
|
||||||
|
abort_if_user_favorites_not_found(user_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
user = db_sess.query(UserFavorites).get({
|
||||||
|
"userId": user_id,
|
||||||
|
"dishId": dish_id
|
||||||
|
})
|
||||||
|
db_sess.delete(user)
|
||||||
|
db_sess.commit()
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
def post(self, user_id, dish_id):
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
user = UserFavorites()
|
||||||
|
user.userId = user_id
|
||||||
|
user.dishId = dish_id
|
||||||
|
|
||||||
|
db_sess.add(user)
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify({"user": user.to_dict()})
|
||||||
|
|
||||||
|
|
||||||
|
class UsersFavoritesListResource(Resource):
|
||||||
|
def get(self, user_id):
|
||||||
|
session = db_session.create_session()
|
||||||
|
user = session.query(Dish).join(UserFavorites).where(UserFavorites.userId == user_id)
|
||||||
|
return jsonify([item.to_dict() for item in user])
|
||||||
|
|
70
server/recources/UserResource.py
Normal file
70
server/recources/UserResource.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
from flask_restful import reqparse, abort, Resource, marshal_with, fields
|
||||||
|
from . import db_session
|
||||||
|
from recources.Model.User import User
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
|
||||||
|
def abort_if_user_not_found(user_id):
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
user = db_sess.query(User).get(user_id)
|
||||||
|
if not user:
|
||||||
|
abort(404, message=f'User {user_id} not found')
|
||||||
|
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument('nickname', required=True)
|
||||||
|
parser.add_argument('email', required=True)
|
||||||
|
parser.add_argument('password', required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class UserResource(Resource):
|
||||||
|
def get(self, user_id):
|
||||||
|
abort_if_user_not_found(user_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
users = db_sess.query(User).get(user_id)
|
||||||
|
return jsonify(
|
||||||
|
users.to_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, user_id):
|
||||||
|
abort_if_user_not_found(user_id)
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
user = db_sess.query(User).get(user_id)
|
||||||
|
db_sess.delete(user)
|
||||||
|
db_sess.commit()
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
def put(self, user_id):
|
||||||
|
abort_if_user_not_found(user_id)
|
||||||
|
args = parser.parse_args()
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
user = db_sess.query(User).get(user_id)
|
||||||
|
user.nickname = args["nickname"]
|
||||||
|
user.email = args["email"]
|
||||||
|
user.set_password(args["password"])
|
||||||
|
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify({'success': 'OK'})
|
||||||
|
|
||||||
|
|
||||||
|
class UsersListResource(Resource):
|
||||||
|
def get(self):
|
||||||
|
session = db_session.create_session()
|
||||||
|
user = session.query(User).all()
|
||||||
|
return jsonify([item.to_dict() for item in user])
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
args = parser.parse_args()
|
||||||
|
db_sess = db_session.create_session()
|
||||||
|
|
||||||
|
user = User()
|
||||||
|
user.nickname = args["nickname"]
|
||||||
|
user.email = args["email"]
|
||||||
|
user.set_password(args["password"])
|
||||||
|
|
||||||
|
db_sess.add(user)
|
||||||
|
db_sess.commit()
|
||||||
|
|
||||||
|
return jsonify(user.to_dict())
|
32
server/recources/db_session.py
Normal file
32
server/recources/db_session.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlalchemy.orm as orm
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
import sqlalchemy.ext.declarative as dec
|
||||||
|
|
||||||
|
SqlAlchemyBase = dec.declarative_base()
|
||||||
|
|
||||||
|
__factory = None
|
||||||
|
|
||||||
|
def global_init(db_file):
|
||||||
|
global __factory
|
||||||
|
|
||||||
|
if __factory:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not db_file or not db_file.strip():
|
||||||
|
raise Exception("Необходимо указать файл базы данных.")
|
||||||
|
|
||||||
|
conn_str = f'sqlite:///{db_file.strip()}?check_same_thread=False'
|
||||||
|
print(f"Подключение к базе данных по адресу {conn_str}")
|
||||||
|
|
||||||
|
engine = sa.create_engine(conn_str, echo=False)
|
||||||
|
__factory = orm.sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
from . import Model
|
||||||
|
|
||||||
|
SqlAlchemyBase.metadata.create_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
def create_session() -> Session:
|
||||||
|
global __factory
|
||||||
|
return __factory()
|
@ -3,6 +3,7 @@ pluginManagement {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
|
Loading…
Reference in New Issue
Block a user