Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
8c81b83717 | |||
756cfa14c0 | |||
7f9450d02e | |||
750e362438 |
@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -23,21 +24,24 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.3"
|
||||
kotlinCompilerExtensionVersion = "1.4.5"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@ -45,17 +49,33 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
//kotlin {
|
||||
//// jvmToolchain(11)
|
||||
//}
|
||||
|
||||
dependencies {
|
||||
|
||||
// Core
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||
implementation("androidx.activity:activity-compose:1.7.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||
|
||||
// UI
|
||||
implementation("androidx.activity:activity-compose:1.7.2")
|
||||
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
|
||||
// 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")
|
||||
|
||||
// Tests
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
@ -3,6 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:name=".FoodWarriorsApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
@ -0,0 +1,44 @@
|
||||
package com.example.myapplication
|
||||
|
||||
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.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.dishRepository,
|
||||
foodWarriorsApplication().container.userFavoritesRepository)
|
||||
}
|
||||
initializer {
|
||||
ErrorsViewModel()
|
||||
}
|
||||
initializer {
|
||||
UserViewModel(foodWarriorsApplication().container.userRepository)
|
||||
}
|
||||
initializer {
|
||||
DishViewModel(foodWarriorsApplication().container.dishRepository)
|
||||
}
|
||||
initializer {
|
||||
DishEditViewModel(
|
||||
this.createSavedStateHandle(),
|
||||
foodWarriorsApplication().container.dishRepository
|
||||
)
|
||||
}
|
||||
initializer {
|
||||
CategoryDropDownViewModel(foodWarriorsApplication().container.categoryRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun CreationExtras.foodWarriorsApplication(): FoodWarriorsApplication =
|
||||
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as FoodWarriorsApplication)
|
@ -0,0 +1,14 @@
|
||||
package com.example.myapplication
|
||||
|
||||
import android.app.Application
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
MyApplicationTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||
Greeting("Android")
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
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,42 @@
|
||||
package com.example.myapplication.database
|
||||
|
||||
import android.content.Context
|
||||
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 userRepository : UserRepository
|
||||
val dishRepository : DishRepository
|
||||
val categoryRepository : CategoryRepository
|
||||
val userFavoritesRepository: UserWithFavoritesRepository
|
||||
}
|
||||
|
||||
class AppDataContainer(private val context: Context) : AppContainer {
|
||||
|
||||
override val userRepository: UserRepository by lazy {
|
||||
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
|
||||
}
|
||||
|
||||
override val dishRepository: DishRepository by lazy {
|
||||
OfflineDishRepository(AppDatabase.getInstance(context).dishDao())
|
||||
}
|
||||
|
||||
override val categoryRepository: CategoryRepository by lazy {
|
||||
OfflineCategoryRepository(AppDatabase.getInstance(context).categoryDao())
|
||||
}
|
||||
|
||||
override val userFavoritesRepository: UserWithFavoritesRepository by lazy {
|
||||
OfflineUserFavoritesRepository(AppDatabase.getInstance(context).userFavoritesDao())
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TIMEOUT = 5000L
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.example.myapplication.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.example.myapplication.database.model.Category
|
||||
import com.example.myapplication.database.model.Dish
|
||||
import com.example.myapplication.database.dao.CategoryDao
|
||||
import com.example.myapplication.database.dao.DishDao
|
||||
import com.example.myapplication.database.model.User
|
||||
import com.example.myapplication.database.model.UserFavorites
|
||||
import com.example.myapplication.database.dao.UserDao
|
||||
import com.example.myapplication.database.dao.UserFavoritesDao
|
||||
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,24 @@
|
||||
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)
|
||||
}
|
@ -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")
|
||||
fun getAll(): Flow<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")
|
||||
fun getByUid(uid: Int): Flow<DishWithCategoryAndUser?>
|
||||
|
||||
@Query("select * from dishes where dishes.user_id = :uid")
|
||||
fun getAllOFUser(uid: Int): Flow<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")
|
||||
fun getAll(): Flow<List<User>>
|
||||
|
||||
@Query("select * from users where users.user_id = :uid")
|
||||
fun getByUid(uid: Int): Flow<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")
|
||||
fun getUserWithFavorites(): Flow<List<Dish>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM user_favorites us JOIN dishes d ON d.dish_id = us.dish_id WHERE us.user_id = :uid")
|
||||
fun getUserFavorites(uid: Int?): Flow<List<Dish>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM user_favorites us WHERE us.user_id = :userUid AND us.dish_id = :dishUid")
|
||||
fun getUserFavorite(userUid: Int?, dishUid: Int?): Flow<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
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CategoryRepository {
|
||||
suspend fun getAllCategories(): List<Category>
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
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 {
|
||||
fun getAllDishes(): Flow<List<Dish>>
|
||||
fun getDish(uid: Int): Flow<DishWithCategoryAndUser?>
|
||||
|
||||
fun getUserDishes(userUid: Int) : Flow<List<Dish>>
|
||||
suspend fun insertDish(dish: Dish)
|
||||
suspend fun updateDish(dish: Dish)
|
||||
suspend fun deleteDish(dish: Dish)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
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 getAllCategories(): List<Category> {
|
||||
return categoryDao.getAll();
|
||||
}
|
||||
}
|
@ -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 fun getAllDishes(): Flow<List<Dish>> {
|
||||
return dishDao.getAll()
|
||||
}
|
||||
|
||||
override fun getDish(uid: Int): Flow<DishWithCategoryAndUser?> {
|
||||
return dishDao.getByUid(uid)
|
||||
}
|
||||
|
||||
override fun getUserDishes(userUid: Int): Flow<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,24 @@
|
||||
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 fun getUserFavorites(userUid: Int): Flow<List<Dish>> {
|
||||
return userFavoritesDao.getUserFavorites(userUid)
|
||||
}
|
||||
|
||||
override fun getUserFavorite(userUid: Int, dishUid: Int): Flow<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 fun getAllUsers(): Flow<List<User>> {
|
||||
return userDao.getAll()
|
||||
}
|
||||
|
||||
override fun getUser(uid: Int): Flow<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 {
|
||||
fun getAllUsers(): Flow<List<User>>
|
||||
fun getUser(uid: Int): Flow<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 {
|
||||
fun getUserFavorites(userUid: Int): Flow<List<Dish>>
|
||||
|
||||
fun getUserFavorite(userUid: Int, dishUid: Int): Flow<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,208 @@
|
||||
package com.example.myapplication.ui.dishes.list
|
||||
|
||||
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.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.mutableStateListOf
|
||||
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.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.model.UserFavorites
|
||||
import com.example.myapplication.database.AppDatabase
|
||||
import com.example.myapplication.database.model.User
|
||||
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.MyApplicationTheme
|
||||
import com.example.myapplication.ui.theme.textFont
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@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 userDishes by viewModel.userDishes(userUid ?: 0).collectAsState()
|
||||
val favorites by viewModel.dishFavorites(userUid ?: 0).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,
|
||||
dishList = when (typeDishList) {
|
||||
TypeDishList.AllDishes -> dishListUiState.dishList
|
||||
TypeDishList.FavoritesDishes -> favorites.dishList
|
||||
TypeDishList.UserDishes -> userDishes.dishList
|
||||
else -> dishListUiState.dishList
|
||||
},
|
||||
favorites = favorites.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,
|
||||
dishList: 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)
|
||||
}
|
||||
}
|
||||
dishList.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
|
||||
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,69 @@
|
||||
package com.example.myapplication.ui.dishes.list
|
||||
|
||||
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.repository.DishRepository
|
||||
import com.example.myapplication.database.repository.UserWithFavoritesRepository
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
enum class TypeDishList {
|
||||
AllDishes,
|
||||
FavoritesDishes,
|
||||
UserDishes
|
||||
}
|
||||
class DishListViewModel(
|
||||
private val dishRepository: DishRepository,
|
||||
private val userWithFavoritesRepository: UserWithFavoritesRepository
|
||||
) : ViewModel() {
|
||||
val dishListUiState: StateFlow<DishListUiState> = dishRepository.getAllDishes().map {
|
||||
DishListUiState(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
||||
initialValue = DishListUiState()
|
||||
)
|
||||
|
||||
fun dishFavorites(userUid: Int) : StateFlow<DishListUiState> {
|
||||
return userWithFavoritesRepository.getUserFavorites(userUid).map {
|
||||
DishListUiState(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
||||
initialValue = DishListUiState()
|
||||
)
|
||||
}
|
||||
|
||||
fun userDishes(userUid: Int) : StateFlow<DishListUiState> {
|
||||
return dishRepository.getUserDishes(userUid).map {
|
||||
DishListUiState(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
||||
initialValue = DishListUiState()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun addFavoritesToUser(userUid: Int, dishUid: Int) {
|
||||
var flag = true;
|
||||
userWithFavoritesRepository.getUserFavorite(userUid, dishUid).collect {
|
||||
if (flag) {
|
||||
if (it == null) {
|
||||
flag = false
|
||||
userWithFavoritesRepository.insertUserFavorites(userUid, dishUid)
|
||||
} else {
|
||||
flag = false
|
||||
userWithFavoritesRepository.deleteUserFavorites(userUid, dishUid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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.getAllCategories())
|
||||
}
|
||||
}
|
||||
|
||||
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 = 1;
|
||||
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
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
val GreyBackground = Color(0xFFE9DFDF);
|
||||
val SkinExtra = Color(0xFFE6CF94)
|
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.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
background = GreyBackground,
|
||||
primary = SkinExtra
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
@ -44,26 +38,9 @@ fun MyApplicationTheme(
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
colorScheme = LightColorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
|
@ -0,0 +1,99 @@
|
||||
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.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
|
||||
|
||||
@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.getUser(userUid ?: 0).collectAsState()
|
||||
|
||||
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,31 @@
|
||||
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.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class UserViewModel(
|
||||
private val userRepository: UserRepository,
|
||||
) : ViewModel() {
|
||||
fun getUser(uid: Int) : StateFlow<UserUiState> {
|
||||
return userRepository.getUser(uid).map {
|
||||
UserUiState(it!!)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
||||
initialValue = UserUiState()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
<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>
|
@ -2,4 +2,5 @@
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.1" 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user