From 118e8a0ea2ee06aee15665329ca06a637d456bea Mon Sep 17 00:00:00 2001 From: shadowik Date: Tue, 19 Dec 2023 22:54:15 +0400 Subject: [PATCH] BackUp --- app/build.gradle.kts | 26 +++- app/src/main/AndroidManifest.xml | 5 +- .../myapplication/AppViewModelProvider.kt | 34 +++-- .../myapplication/FoodWarriorsApplication.kt | 1 + .../example/myapplication/api/ApiRoutes.kt | 6 + .../myapplication/api/ServerService.kt | 134 ++++++++++++++++++ .../myapplication/api/model/CategoryRemote.kt | 20 +++ .../myapplication/api/model/DishRemote.kt | 35 +++++ .../api/model/UserFavoritesRemote.kt | 21 +++ .../myapplication/api/model/UserRemote.kt | 27 ++++ .../api/repository/RestCategoryRepository.kt | 26 ++++ .../api/repository/RestDishRepository.kt | 43 ++++++ .../repository/RestUserFavoritesRepository.kt | 32 +++++ .../api/repository/RestUserRepository.kt | 42 ++++++ .../myapplication/database/AppContainer.kt | 51 +++++-- .../myapplication/database/AppDatabase.kt | 23 ++- .../myapplication/database/PrefRepository.kt | 15 ++ .../myapplication/database/dao/CategoryDao.kt | 3 + .../myapplication/database/dao/DishDao.kt | 6 +- .../myapplication/database/dao/UserDao.kt | 4 +- .../database/dao/UserFavoritesDao.kt | 6 +- .../database/repository/CategoryRepository.kt | 4 +- .../database/repository/DishRepository.kt | 7 +- .../repository/OfflineCategoryRepository.kt | 7 +- .../repository/OfflineDishRepository.kt | 6 +- .../OfflineUserFavoritesRepository.kt | 5 +- .../repository/OfflineUserRepository.kt | 4 +- .../database/repository/UserRepository.kt | 4 +- .../repository/UserWithFavoritesRepository.kt | 4 +- .../myapplication/ui/dishes/list/DishList.kt | 35 ++--- .../ui/dishes/list/DishListViewModel.kt | 79 ++++++----- .../dishes/view/CategoryDropDownViewModel.kt | 2 +- .../ui/dishes/view/DishEditViewModel.kt | 20 +-- .../myapplication/ui/dishes/view/DishView.kt | 54 +++---- .../ui/dishes/view/DishViewModel.kt | 18 +-- .../myapplication/ui/navigation/MainNavbar.kt | 2 +- .../example/myapplication/ui/user/UserView.kt | 13 +- .../myapplication/ui/user/UserViewModel.kt | 18 +-- .../main/res/xml/network_security_config.xml | 6 + build.gradle.kts | 1 + server/app.py | 34 +++++ server/dishWarriors.db | Bin 0 -> 24576 bytes server/recources/CategoryResource.py | 64 +++++++++ server/recources/DishResource.py | 82 +++++++++++ server/recources/Model/Category.py | 11 ++ server/recources/Model/Dish.py | 15 ++ server/recources/Model/User.py | 21 +++ server/recources/Model/UserFavorites.py | 10 ++ server/recources/Model/__init__.py | 1 + server/recources/UserFavoritesResource.py | 60 ++++++++ server/recources/UserResource.py | 70 +++++++++ server/recources/db_session.py | 32 +++++ settings.gradle.kts | 1 + 53 files changed, 1071 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/com/example/myapplication/api/ApiRoutes.kt create mode 100644 app/src/main/java/com/example/myapplication/api/ServerService.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/DishRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/UserFavoritesRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/model/UserRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/RestDishRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/RestUserFavoritesRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/api/repository/RestUserRepository.kt create mode 100644 app/src/main/java/com/example/myapplication/database/PrefRepository.kt create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 server/app.py create mode 100644 server/dishWarriors.db create mode 100644 server/recources/CategoryResource.py create mode 100644 server/recources/DishResource.py create mode 100644 server/recources/Model/Category.py create mode 100644 server/recources/Model/Dish.py create mode 100644 server/recources/Model/User.py create mode 100644 server/recources/Model/UserFavorites.py create mode 100644 server/recources/Model/__init__.py create mode 100644 server/recources/UserFavoritesResource.py create mode 100644 server/recources/UserResource.py create mode 100644 server/recources/db_session.py diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 409dba3..c09fd6c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,7 +1,10 @@ +import org.apache.tools.ant.util.JavaEnvUtils.VERSION_11 + plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") + id("org.jetbrains.kotlin.plugin.serialization") } android { @@ -31,11 +34,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "17" + jvmTarget = "11" } buildFeatures { compose = true @@ -49,9 +52,10 @@ android { } } } -//kotlin { -//// jvmToolchain(11) -//} + +kotlin { + jvmToolchain(17) +} dependencies { // Core @@ -75,6 +79,14 @@ dependencies { 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") androidTestImplementation("androidx.test.ext:junit:1.1.5") @@ -83,4 +95,6 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") + + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a12c956..fa4f8f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + tools:targetApi="31" + android:networkSecurityConfig="@xml/network_security_config"> + + @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 + + @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 + + @GET(ApiRoutes.PREFIX + "user_dish/{id}") + suspend fun getAllUserDishes( + @Path("id") userId: Int + ): List + + @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 + + @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 } + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt b/app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt new file mode 100644 index 0000000..e9b1f45 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/CategoryRemote.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/DishRemote.kt b/app/src/main/java/com/example/myapplication/api/model/DishRemote.kt new file mode 100644 index 0000000..66fd6e6 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/DishRemote.kt @@ -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!! +) diff --git a/app/src/main/java/com/example/myapplication/api/model/UserFavoritesRemote.kt b/app/src/main/java/com/example/myapplication/api/model/UserFavoritesRemote.kt new file mode 100644 index 0000000..a316cd4 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/UserFavoritesRemote.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt b/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt new file mode 100644 index 0000000..f36e6ca --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt new file mode 100644 index 0000000..4444052 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/RestCategoryRepository.kt @@ -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 { + 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()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/RestDishRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/RestDishRepository.kt new file mode 100644 index 0000000..6dd725f --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/RestDishRepository.kt @@ -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 { + 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 { + 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!!) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/RestUserFavoritesRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/RestUserFavoritesRepository.kt new file mode 100644 index 0000000..f69c264 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/RestUserFavoritesRepository.kt @@ -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 { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/repository/RestUserRepository.kt b/app/src/main/java/com/example/myapplication/api/repository/RestUserRepository.kt new file mode 100644 index 0000000..fe23f27 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/repository/RestUserRepository.kt @@ -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 { + 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!!) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/AppContainer.kt b/app/src/main/java/com/example/myapplication/database/AppContainer.kt index fb158cb..54020f4 100644 --- a/app/src/main/java/com/example/myapplication/database/AppContainer.kt +++ b/app/src/main/java/com/example/myapplication/database/AppContainer.kt @@ -1,6 +1,11 @@ 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 @@ -12,30 +17,60 @@ import com.example.myapplication.database.repository.UserWithFavoritesRepository interface AppContainer { - val userRepository : UserRepository - val dishRepository : DishRepository - val categoryRepository : CategoryRepository - val userFavoritesRepository: UserWithFavoritesRepository + val userRestRepository : UserRepository + val dishRestRepository : DishRepository + val categoryRestRepository : CategoryRepository + val userFavoritesRestRepository: UserWithFavoritesRepository } class AppDataContainer(private val context: Context) : AppContainer { - override val userRepository: UserRepository by lazy { + val userRepository: OfflineUserRepository by lazy { OfflineUserRepository(AppDatabase.getInstance(context).userDao()) } - override val dishRepository: DishRepository by lazy { + val dishRepository: OfflineDishRepository by lazy { OfflineDishRepository(AppDatabase.getInstance(context).dishDao()) } - override val categoryRepository: CategoryRepository by lazy { + val categoryRepository: OfflineCategoryRepository by lazy { OfflineCategoryRepository(AppDatabase.getInstance(context).categoryDao()) } - override val userFavoritesRepository: UserWithFavoritesRepository by lazy { + 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 } diff --git a/app/src/main/java/com/example/myapplication/database/AppDatabase.kt b/app/src/main/java/com/example/myapplication/database/AppDatabase.kt index 1c575f2..dde940d 100644 --- a/app/src/main/java/com/example/myapplication/database/AppDatabase.kt +++ b/app/src/main/java/com/example/myapplication/database/AppDatabase.kt @@ -1,24 +1,35 @@ 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.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 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) + +@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 diff --git a/app/src/main/java/com/example/myapplication/database/PrefRepository.kt b/app/src/main/java/com/example/myapplication/database/PrefRepository.kt new file mode 100644 index 0000000..a053e9e --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/PrefRepository.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt b/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt index 739c875..e44403f 100644 --- a/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt +++ b/app/src/main/java/com/example/myapplication/database/dao/CategoryDao.kt @@ -21,4 +21,7 @@ interface CategoryDao { @Delete suspend fun delete(category: Category) + + @Query("DELETE FROM categories") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/dao/DishDao.kt b/app/src/main/java/com/example/myapplication/database/dao/DishDao.kt index 171ab27..121bd87 100644 --- a/app/src/main/java/com/example/myapplication/database/dao/DishDao.kt +++ b/app/src/main/java/com/example/myapplication/database/dao/DishDao.kt @@ -12,14 +12,14 @@ import kotlinx.coroutines.flow.Flow @Dao interface DishDao { @Query("select * from dishes order by dish_name collate nocase asc") - fun getAll(): Flow> + suspend fun getAll(): List @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 + suspend fun getByUid(uid: Int): Dish? @Query("select * from dishes where dishes.user_id = :uid") - fun getAllOFUser(uid: Int): Flow> + suspend fun getAllOFUser(uid: Int): List @Insert suspend fun insert(category: Dish) diff --git a/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt b/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt index c2b1fe0..96bc2a6 100644 --- a/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt +++ b/app/src/main/java/com/example/myapplication/database/dao/UserDao.kt @@ -12,10 +12,10 @@ import kotlinx.coroutines.flow.Flow @Dao interface UserDao { @Query("select * from users") - fun getAll(): Flow> + suspend fun getAll(): List @Query("select * from users where users.user_id = :uid") - fun getByUid(uid: Int): Flow + suspend fun getByUid(uid: Int): User? @Query("select * from user_favorites left join users on user_favorites.user_id = users.user_id " + diff --git a/app/src/main/java/com/example/myapplication/database/dao/UserFavoritesDao.kt b/app/src/main/java/com/example/myapplication/database/dao/UserFavoritesDao.kt index 8c135ed..a0a3fe6 100644 --- a/app/src/main/java/com/example/myapplication/database/dao/UserFavoritesDao.kt +++ b/app/src/main/java/com/example/myapplication/database/dao/UserFavoritesDao.kt @@ -24,13 +24,13 @@ interface UserFavoritesDao { @Transaction @Query("SELECT * FROM dishes") - fun getUserWithFavorites(): Flow> + suspend fun getUserWithFavorites(): List @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> + suspend fun getUserFavorites(uid: Int?): List @Transaction @Query("SELECT * FROM user_favorites us WHERE us.user_id = :userUid AND us.dish_id = :dishUid") - fun getUserFavorite(userUid: Int?, dishUid: Int?): Flow + suspend fun getUserFavorite(userUid: Int?, dishUid: Int?): UserFavorites? } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt index 622a6c8..0ae88d9 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/CategoryRepository.kt @@ -1,8 +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 + suspend fun getAll(): List + suspend fun insert(category: Category) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/DishRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/DishRepository.kt index 151f789..e43965f 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/DishRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/DishRepository.kt @@ -5,10 +5,9 @@ import com.example.myapplication.database.model.DishWithCategoryAndUser import kotlinx.coroutines.flow.Flow interface DishRepository { - fun getAllDishes(): Flow> - fun getDish(uid: Int): Flow - - fun getUserDishes(userUid: Int) : Flow> + suspend fun getAllDishes(): List + suspend fun getDish(uid: Int): Dish? + suspend fun getUserDishes(userUid: Int) : List suspend fun insertDish(dish: Dish) suspend fun updateDish(dish: Dish) suspend fun deleteDish(dish: Dish) diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt index 7cfdac9..b36c8d5 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineCategoryRepository.kt @@ -5,7 +5,12 @@ import com.example.myapplication.database.model.Category import kotlinx.coroutines.flow.Flow class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository { - override suspend fun getAllCategories(): List { + override suspend fun getAll(): List { return categoryDao.getAll(); } + + override suspend fun insert(category: Category) = categoryDao.insert(category) + + suspend fun deleteAll() = categoryDao.deleteAll() + } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineDishRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineDishRepository.kt index c05248e..369e2f2 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineDishRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineDishRepository.kt @@ -6,15 +6,15 @@ import com.example.myapplication.database.model.DishWithCategoryAndUser import kotlinx.coroutines.flow.Flow class OfflineDishRepository(private val dishDao: DishDao) : DishRepository { - override fun getAllDishes(): Flow> { + override suspend fun getAllDishes(): List { return dishDao.getAll() } - override fun getDish(uid: Int): Flow { + override suspend fun getDish(uid: Int): Dish? { return dishDao.getByUid(uid) } - override fun getUserDishes(userUid: Int): Flow> { + override suspend fun getUserDishes(userUid: Int): List { return dishDao.getAllOFUser(userUid); } diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineUserFavoritesRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineUserFavoritesRepository.kt index 5844cab..d47455d 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineUserFavoritesRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineUserFavoritesRepository.kt @@ -6,14 +6,15 @@ 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> { + override suspend fun getUserFavorites(userUid: Int): List { return userFavoritesDao.getUserFavorites(userUid) } - override fun getUserFavorite(userUid: Int, dishUid: Int): Flow { + 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)) } diff --git a/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt index 13e0d8e..44600bc 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/OfflineUserRepository.kt @@ -5,11 +5,11 @@ import com.example.myapplication.database.model.User import kotlinx.coroutines.flow.Flow class OfflineUserRepository(private val userDao: UserDao) : UserRepository { - override fun getAllUsers(): Flow> { + override suspend fun getAllUsers(): List { return userDao.getAll() } - override fun getUser(uid: Int): Flow { + override suspend fun getUser(uid: Int): User? { return userDao.getByUid(uid) } diff --git a/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt index 4826f36..710a298 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/UserRepository.kt @@ -4,8 +4,8 @@ import com.example.myapplication.database.model.User import kotlinx.coroutines.flow.Flow interface UserRepository { - fun getAllUsers(): Flow> - fun getUser(uid: Int): Flow + suspend fun getAllUsers(): List + suspend fun getUser(uid: Int): User? suspend fun insertUser(user: User) suspend fun updateUser(user: User) suspend fun deleteUser(user: User) diff --git a/app/src/main/java/com/example/myapplication/database/repository/UserWithFavoritesRepository.kt b/app/src/main/java/com/example/myapplication/database/repository/UserWithFavoritesRepository.kt index 72970e1..4c06734 100644 --- a/app/src/main/java/com/example/myapplication/database/repository/UserWithFavoritesRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/repository/UserWithFavoritesRepository.kt @@ -5,9 +5,9 @@ import com.example.myapplication.database.model.UserFavorites import kotlinx.coroutines.flow.Flow interface UserWithFavoritesRepository { - fun getUserFavorites(userUid: Int): Flow> + suspend fun getUserFavorites(userUid: Int): List - fun getUserFavorite(userUid: Int, dishUid: Int): Flow + suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites? suspend fun deleteUserFavorites(userUid: Int, dishUid: Int) diff --git a/app/src/main/java/com/example/myapplication/ui/dishes/list/DishList.kt b/app/src/main/java/com/example/myapplication/ui/dishes/list/DishList.kt index 8f90e19..bb75d5d 100644 --- a/app/src/main/java/com/example/myapplication/ui/dishes/list/DishList.kt +++ b/app/src/main/java/com/example/myapplication/ui/dishes/list/DishList.kt @@ -1,5 +1,6 @@ package com.example.myapplication.ui.dishes.list +import android.content.SharedPreferences import android.os.Build import androidx.annotation.RequiresApi import androidx.compose.foundation.Image @@ -19,20 +20,14 @@ 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 @@ -44,17 +39,12 @@ 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.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.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) @@ -67,8 +57,10 @@ fun DishList( ) { 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() + val dishFavoritesUiState by viewModel.favoritesDishListUiState.collectAsState() + val dishUserUiState by viewModel.userDishListUiState.collectAsState() + + Scaffold( ) { innerPadding -> if (typeDishList != TypeDishList.AllDishes && userUid == null) { @@ -81,13 +73,13 @@ fun DishList( .fillMaxSize() .verticalScroll(ScrollState(0)), typeDishList = typeDishList, - dishList = when (typeDishList) { + dishListTytle = when (typeDishList) { TypeDishList.AllDishes -> dishListUiState.dishList - TypeDishList.FavoritesDishes -> favorites.dishList - TypeDishList.UserDishes -> userDishes.dishList + TypeDishList.FavoritesDishes -> dishFavoritesUiState.dishList + TypeDishList.UserDishes -> dishUserUiState.dishList else -> dishListUiState.dishList }, - favorites = favorites.dishList, + favorites = dishFavoritesUiState.dishList, addToFavorites = {uid: Int -> if (userUid != null) { coroutineScope.launch { @@ -109,7 +101,7 @@ fun DishList( private fun DishList( modifier: Modifier = Modifier, typeDishList: TypeDishList, - dishList: List, + dishListTytle: List, favorites: List, addToFavorites: (uid : Int) -> Unit, onClick: (uid : Int) -> Unit @@ -130,7 +122,7 @@ private fun DishList( fontSize=26.sp, textAlign = TextAlign.Start) } } - dishList.forEachIndexed() {index, dish -> + dishListTytle.forEachIndexed() { index, dish -> DishListItem(index=index, dish = dish, favorites = favorites, addToFavorites, onClick = onClick) } } @@ -154,7 +146,8 @@ private fun DishListItem( }) { Column() { - if (dish.image != null) { // TODO Image input check +// if (dish.image != null) { // TODO Image input check + if (false) { Image( bitmap = dish.getBitmapFromByteArray()!!.asImageBitmap(), contentDescription = "Dish Image", diff --git a/app/src/main/java/com/example/myapplication/ui/dishes/list/DishListViewModel.kt b/app/src/main/java/com/example/myapplication/ui/dishes/list/DishListViewModel.kt index 7c61a87..5eb880a 100644 --- a/app/src/main/java/com/example/myapplication/ui/dishes/list/DishListViewModel.kt +++ b/app/src/main/java/com/example/myapplication/ui/dishes/list/DishListViewModel.kt @@ -1,16 +1,24 @@ 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, @@ -19,48 +27,45 @@ enum class TypeDishList { } class DishListViewModel( private val dishRepository: DishRepository, - private val userWithFavoritesRepository: UserWithFavoritesRepository + private val userWithFavoritesRepository: UserWithFavoritesRepository, ) : ViewModel() { - val dishListUiState: StateFlow = dishRepository.getAllDishes().map { - DishListUiState(it) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), - initialValue = DishListUiState() - ) - fun dishFavorites(userUid: Int) : StateFlow { - return userWithFavoritesRepository.getUserFavorites(userUid).map { - DishListUiState(it) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), - initialValue = DishListUiState() - ) - } + private val _dishListUiState = MutableStateFlow(DishListUiState()) + val dishListUiState: StateFlow = _dishListUiState.asStateFlow() - fun userDishes(userUid: Int) : StateFlow { - return dishRepository.getUserDishes(userUid).map { - DishListUiState(it) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), - initialValue = DishListUiState() - ) - } + private val _userDishListUiState = MutableStateFlow(DishListUiState()) + val userDishListUiState: StateFlow = _dishListUiState.asStateFlow() - 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) - } + private val _favoritesDishListUiState = MutableStateFlow(DishListUiState()) + val favoritesDishListUiState: StateFlow = _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)) } } } diff --git a/app/src/main/java/com/example/myapplication/ui/dishes/view/CategoryDropDownViewModel.kt b/app/src/main/java/com/example/myapplication/ui/dishes/view/CategoryDropDownViewModel.kt index 7954da6..65b7383 100644 --- a/app/src/main/java/com/example/myapplication/ui/dishes/view/CategoryDropDownViewModel.kt +++ b/app/src/main/java/com/example/myapplication/ui/dishes/view/CategoryDropDownViewModel.kt @@ -20,7 +20,7 @@ class CategoryDropDownViewModel( init { viewModelScope.launch { - categoryListUiState = CategoryListUiState(categoryRepository.getAllCategories()) + categoryListUiState = CategoryListUiState(categoryRepository.getAll()) } } diff --git a/app/src/main/java/com/example/myapplication/ui/dishes/view/DishEditViewModel.kt b/app/src/main/java/com/example/myapplication/ui/dishes/view/DishEditViewModel.kt index 72483e9..25dac02 100644 --- a/app/src/main/java/com/example/myapplication/ui/dishes/view/DishEditViewModel.kt +++ b/app/src/main/java/com/example/myapplication/ui/dishes/view/DishEditViewModel.kt @@ -23,16 +23,16 @@ class DishEditViewModel ( private val dishUid: Int = checkNotNull(savedStateHandle["id"]) - init { - viewModelScope.launch { - if (dishUid > 0) { - dishEditUiState = dishRepository.getDish(dishUid) - .filterNotNull() - .first().dish!! - .toUiState(true) - } - } - } +// init { +// viewModelScope.launch { +// if (dishUid > 0) { +// dishEditUiState = dishRepository.getDish(dishUid) +// .filterNotNull() +// .first().dish!! +// .toUiState(true) +// } +// } +// } fun updateUiState(dishDetails: DishDetails) { dishEditUiState = DishEditUiState( dishDetails = dishDetails, diff --git a/app/src/main/java/com/example/myapplication/ui/dishes/view/DishView.kt b/app/src/main/java/com/example/myapplication/ui/dishes/view/DishView.kt index 3402e8b..1f10079 100644 --- a/app/src/main/java/com/example/myapplication/ui/dishes/view/DishView.kt +++ b/app/src/main/java/com/example/myapplication/ui/dishes/view/DishView.kt @@ -62,33 +62,33 @@ fun DishView(navController: NavController, 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!! - ) - } - } +// 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) diff --git a/app/src/main/java/com/example/myapplication/ui/dishes/view/DishViewModel.kt b/app/src/main/java/com/example/myapplication/ui/dishes/view/DishViewModel.kt index 7ff28f3..f42ba2b 100644 --- a/app/src/main/java/com/example/myapplication/ui/dishes/view/DishViewModel.kt +++ b/app/src/main/java/com/example/myapplication/ui/dishes/view/DishViewModel.kt @@ -13,15 +13,15 @@ import kotlinx.coroutines.flow.stateIn class DishViewModel( private val dishRepository: DishRepository ) : ViewModel() { - fun getDish(uid: Int) : StateFlow { - return dishRepository.getDish(uid).map { - DishViewUiState(it!!) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), - initialValue = DishViewUiState() - ) - } +// fun getDish(uid: Int) : StateFlow { +// 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) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt b/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt index 7480099..892edbe 100644 --- a/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt @@ -40,7 +40,7 @@ fun Navhost( innerPadding: PaddingValues, modifier: Modifier = Modifier ) { - val userUid = 1; + val userUid = 2; NavHost( navController, startDestination = Screen.AllDishes.route, diff --git a/app/src/main/java/com/example/myapplication/ui/user/UserView.kt b/app/src/main/java/com/example/myapplication/ui/user/UserView.kt index e96a2ce..6228565 100644 --- a/app/src/main/java/com/example/myapplication/ui/user/UserView.kt +++ b/app/src/main/java/com/example/myapplication/ui/user/UserView.kt @@ -20,6 +20,7 @@ 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 @@ -35,6 +36,8 @@ 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) @@ -45,7 +48,11 @@ fun UserView ( userUid: Int? ) { val coroutineScope = rememberCoroutineScope() - val userUiState by viewModel.getUser(userUid ?: 0).collectAsState() + val userUiState by viewModel.userState.collectAsState() + + coroutineScope.let { + it.launch { viewModel.getUser(userUid ?: 0) } + } Scaffold( floatingActionButton = { @@ -66,8 +73,8 @@ fun UserView ( UserView( navController = navController, modifier = Modifier - .padding(innerPadding) - .fillMaxSize(), + .padding(innerPadding) + .fillMaxSize(), user = userUiState.user!! ) } diff --git a/app/src/main/java/com/example/myapplication/ui/user/UserViewModel.kt b/app/src/main/java/com/example/myapplication/ui/user/UserViewModel.kt index 8108f2d..373c8f3 100644 --- a/app/src/main/java/com/example/myapplication/ui/user/UserViewModel.kt +++ b/app/src/main/java/com/example/myapplication/ui/user/UserViewModel.kt @@ -9,22 +9,24 @@ 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() { - fun getUser(uid: Int) : StateFlow { - return userRepository.getUser(uid).map { - UserUiState(it!!) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), - initialValue = UserUiState() - ) + + private val _userState = MutableStateFlow(UserUiState()) + val userState: StateFlow = _userState.asStateFlow() + suspend fun getUser(uid: Int) { + _userState.update { currentState -> + currentState.copy(userRepository.getUser(uid)) + } } } diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..eb7159c --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + 10.0.2.2 + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bd1eb8a..77edee4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,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 + id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply false } \ No newline at end of file diff --git a/server/app.py b/server/app.py new file mode 100644 index 0000000..94d1d2e --- /dev/null +++ b/server/app.py @@ -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/") +api.add_resource(CategoryListResource, '/categories') +api.add_resource(CategoryResource, "/category/") +api.add_resource(DishListResource, '/dishes') +api.add_resource(DishResource, "/dish/") +api.add_resource(DishUserListResource, "/user_dish/") +api.add_resource(UserFavoriteResource, "/favorite//") +api.add_resource(UsersFavoritesListResource, "/favorites/") + + +@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') \ No newline at end of file diff --git a/server/dishWarriors.db b/server/dishWarriors.db new file mode 100644 index 0000000000000000000000000000000000000000..aa7b055322fd788b4a2b9d5e5792343bdbaf8c5c GIT binary patch literal 24576 zcmeI(O-~y~7y#g1V{Gi=&_h})QX`g>1Flju`#C#QkuY?jZd^VL1aY!vc4oo`n{~Yg z3YQ96wdz0WkLa(+FX$oap{LH8FPj7@xi#=wUcB?}?9RN;J3fpoHrDG=PZ%=pwrS6> zwNEu&*X9jF)3k@`$f@JY2hMLheW=au|5+*>X}NJnoA~3w-=)7wKaV@5 zx!Z1lI0%3M2!H?xfB*=900@A<|4ZO(WHeu{RPTx-;Rs5W+t z=fRFKbNQI3#^pVg^Z`q&ji9!?lJ>dkQZX9AQqTxi7K4rcgc4&W;%|4q7<;ID8WZRJ zi7-r~}i+FTZwbRct`Et3epM7$*cKb^<85t6~80vPQDBQb= zB-%7;4f5<#wOn|!LzCnv?()G)H}fx64)lC^YDzzK`}4Y<_w`w3XolDE^n3K4b$ol+ zl3IlN#6OyP!wUpJ00ck)1V8`;KmY_l00ck)1VG?U35@9@T6rxxIictK|7Y67vj@lH zZ^qx;sWc%O5C8!X009sH0T2KI5CDODFK{}a&1sLnK3h+ADUXk$y%V}iyR&WD-J557 zv)u#rWA|~VH)mVe^XL3Ib9!~Vy}7~L)jHuzV%u&_TaL>K7a>O$aeRcCO{7m8&M1){ zwj63(rc03ROB_;69iRBVN0G^FPJ|3i4>5|c#SjgN>r&6eoM2ZXMi|00WIi{s?{bVe zbNo;}3nSuswu?F93<;rrwEgYFQovDbD|WW3Xt}n_zCYfW#;!%AWt*mp2$eRXrsSS< zLf@8ZRD^vhY$1uS60<2n922T0L9vu5bUh|~W;vc?dalPq*F+(+v9A&kOiCg{WP02V zrO71q5%H)rrGss0+m2*RbwMhF-#_-1csif?z=agO+UayQ7G?Y@{B}J##M72#a_&*$ zV26_syM8Ea8oG@7%#l8J8RM8y5vqj}+7vTSc+$0z<+>Jfg~LQB(xv1|&zFQ?+x9T! zi1?mbG?t|npJ_Xut1ek8f{Cgv@i_Hd(;|$iQlIbtUuzSuKlWn4{6PQ&KmY_l00ck) z1V8`;KmY_l;64j{q35;9FUR%5n&@;yH{OrxaaXjBsFNJDGwJ?c{nNiT@$Y>$g4{s> z1V8`;KmY_l00ck)1V8`;K;W(lWc3lfkSV6=!T$fQ78&va0T2KI5C8!X009sH0T2KI O5V-dOqglOB$ovOkN$6() literal 0 HcmV?d00001 diff --git a/server/recources/CategoryResource.py b/server/recources/CategoryResource.py new file mode 100644 index 0000000..69f461b --- /dev/null +++ b/server/recources/CategoryResource.py @@ -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'}) \ No newline at end of file diff --git a/server/recources/DishResource.py b/server/recources/DishResource.py new file mode 100644 index 0000000..4c236cc --- /dev/null +++ b/server/recources/DishResource.py @@ -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]) diff --git a/server/recources/Model/Category.py b/server/recources/Model/Category.py new file mode 100644 index 0000000..1a977c8 --- /dev/null +++ b/server/recources/Model/Category.py @@ -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) diff --git a/server/recources/Model/Dish.py b/server/recources/Model/Dish.py new file mode 100644 index 0000000..f473eed --- /dev/null +++ b/server/recources/Model/Dish.py @@ -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")) diff --git a/server/recources/Model/User.py b/server/recources/Model/User.py new file mode 100644 index 0000000..173ea5f --- /dev/null +++ b/server/recources/Model/User.py @@ -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) \ No newline at end of file diff --git a/server/recources/Model/UserFavorites.py b/server/recources/Model/UserFavorites.py new file mode 100644 index 0000000..72ae7e3 --- /dev/null +++ b/server/recources/Model/UserFavorites.py @@ -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) \ No newline at end of file diff --git a/server/recources/Model/__init__.py b/server/recources/Model/__init__.py new file mode 100644 index 0000000..b6be881 --- /dev/null +++ b/server/recources/Model/__init__.py @@ -0,0 +1 @@ +from . import Category, Dish, User, UserFavorites diff --git a/server/recources/UserFavoritesResource.py b/server/recources/UserFavoritesResource.py new file mode 100644 index 0000000..c2de7df --- /dev/null +++ b/server/recources/UserFavoritesResource.py @@ -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]) + diff --git a/server/recources/UserResource.py b/server/recources/UserResource.py new file mode 100644 index 0000000..044bc16 --- /dev/null +++ b/server/recources/UserResource.py @@ -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()) \ No newline at end of file diff --git a/server/recources/db_session.py b/server/recources/db_session.py new file mode 100644 index 0000000..fc0e78b --- /dev/null +++ b/server/recources/db_session.py @@ -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() \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 579fe75..93e3f1d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ pluginManagement { google() mavenCentral() gradlePluginPortal() + } } dependencyResolutionManagement {