Compare commits

...

5 Commits

15 changed files with 242 additions and 44 deletions

View File

@ -3,6 +3,7 @@ package com.example.shawarma
import android.app.Application import android.app.Application
import androidx.room.Room import androidx.room.Room
import com.example.shawarma.data.api.MyServerService import com.example.shawarma.data.api.MyServerService
import com.example.shawarma.data.api.repos.RestProductRepository
import com.example.shawarma.data.api.repos.RestUserRepository import com.example.shawarma.data.api.repos.RestUserRepository
import com.example.shawarma.data.db.AppDatabase import com.example.shawarma.data.db.AppDatabase
import com.example.shawarma.data.repos.OrderProductRepository import com.example.shawarma.data.repos.OrderProductRepository
@ -49,7 +50,13 @@ object AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideProductRepository(db: AppDatabase) : ProductRepository { fun provideProductRepository(db: AppDatabase) : ProductRepository {
return ProductRepository(db.productDao(), db.orderProductDao()) return ProductRepository(database = db, db.productDao(), db.orderProductDao())
}
@Provides
@Singleton
fun provideRestProductRepository(service: MyServerService) : RestProductRepository {
return RestProductRepository(service)
} }
@Provides @Provides

View File

@ -1,5 +1,6 @@
package com.example.shawarma.data.api package com.example.shawarma.data.api
import com.example.shawarma.data.api.models.ProductListResponse
import com.example.shawarma.data.api.models.TokenModelRemote import com.example.shawarma.data.api.models.TokenModelRemote
import com.example.shawarma.data.api.models.UserModelRemote import com.example.shawarma.data.api.models.UserModelRemote
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
@ -8,7 +9,10 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path
interface MyServerService { interface MyServerService {
@ -22,6 +26,20 @@ interface MyServerService {
@Body user: UserModelRemote @Body user: UserModelRemote
) : TokenModelRemote ) : TokenModelRemote
@POST("check/login")
suspend fun checkLogin(
@Body user: UserModelRemote
) :UserModelRemote?
@GET("user/products/{after}")
suspend fun getProductsList(@Path("after") after: Int, @Header("Authorization") token: String) : ProductListResponse
@GET("user/discounts/{after}")
suspend fun getDiscountsList(@Path("after") after: Int, @Header("Authorization") token: String) : ProductListResponse
@GET("user/items/{after}")
suspend fun getItemsList(@Path("after") after: Int, @Header("Authorization") token: String) : ProductListResponse
companion object { companion object {
private const val BASE_URL = "https://10.0.2.2:80/api/" private const val BASE_URL = "https://10.0.2.2:80/api/"

View File

@ -0,0 +1,91 @@
package com.example.shawarma.data.api.mediators
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.shawarma.data.api.MyServerService
import com.example.shawarma.data.api.models.ProductListResponse
import com.example.shawarma.data.api.models.toProductModel
import com.example.shawarma.data.db.AppDatabase
import com.example.shawarma.data.models.ProductModel
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class ProductRemoteMediator (
private val database: AppDatabase,
private val serverService: MyServerService,
private val query: String,
private val token: String
) : RemoteMediator<Int, ProductModel>(){
private val productDao = database.productDao()
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, ProductModel>
): MediatorResult {
return try {
// The network load method takes an optional `after=<user.id>` parameter. For every
// page after the first, we pass the last user ID to let it continue from where it
// left off. For REFRESH, pass `null` to load the first page.
var loadKey = when (loadType) {
LoadType.REFRESH -> null
// In this example, we never need to prepend, since REFRESH will always load the
// first page in the list. Immediately return, reporting end of pagination.
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull()
?: return MediatorResult.Success(endOfPaginationReached = true)
// We must explicitly check if the last item is `null` when appending,
// since passing `null` to networkService is only valid for initial load.
// If lastItem is `null` it means no items were loaded after the initial
// REFRESH and there are no more items to load.
lastItem.id
}
}
// Suspending network load via Retrofit. This doesn't need to be wrapped in a
// withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter
// dispatches on a worker thread.
if (loadKey == null) {
loadKey = 0
}
val response: ProductListResponse = when (query) {
"home" -> {
serverService.getProductsList(after = loadKey, token = token)
}
"discounts" -> {
serverService.getDiscountsList(after = loadKey, token = token)
}
"items" -> {
serverService.getItemsList(after = loadKey, token = token)
}
else -> {ProductListResponse()}
}
database.withTransaction {
if (loadType == LoadType.REFRESH) {
productDao.deleteByQuery(query)
}
// Insert new users into database, which invalidates the current
// PagingData, allowing Paging to present the updates in the DB.
val products = mutableListOf<ProductModel>()
for (prod in response.products) {
products.add(prod.toProductModel())
}
productDao.insertAll(products)
}
MediatorResult.Success(endOfPaginationReached = response.nextKey == -1)
} catch (e: IOException) {
MediatorResult.Error(e)
}
}
}

View File

@ -0,0 +1,26 @@
package com.example.shawarma.data.api.models
import com.example.shawarma.data.models.ProductModel
import kotlinx.serialization.Serializable
@Serializable
data class ProductModelRemote(
val id: Int = 0,
val title: String = "",
val price: Int = 0,
val old_price: Int? = null
)
@Serializable
data class ProductListResponse(
val products: List<ProductModelRemote> = listOf(),
val nextKey: Int? = null
)
fun ProductModelRemote.toProductModel(): ProductModel = ProductModel(
id, title, price, old_price
)
fun ProductModel.toProductModelRemote(): ProductModelRemote = ProductModelRemote(
title = title, price = price, old_price = oldPrice
)

View File

@ -5,16 +5,17 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class TokenModelRemote( data class TokenModelRemote(
val access_token: String = "" val access_token: String = "",
val message: String = ""
) )
@Serializable @Serializable
data class UserModelRemote( data class UserModelRemote(
val id: Int = 0, val id: Int = 0,
val login: String = "", val login: String = "",
val password:String = "", val password:String = "",
val role:String = "" val role:String = "",
val message: String = ""
) )
fun UserModelRemote.toUserModel(): UserModel = UserModel( fun UserModelRemote.toUserModel(): UserModel = UserModel(

View File

@ -0,0 +1,10 @@
package com.example.shawarma.data.api.repos
import com.example.shawarma.data.api.MyServerService
import javax.inject.Inject
class RestProductRepository @Inject constructor(
private val service: MyServerService
) {
}

View File

@ -2,6 +2,7 @@ package com.example.shawarma.data.api.repos
import com.example.shawarma.data.api.MyServerService import com.example.shawarma.data.api.MyServerService
import com.example.shawarma.data.api.models.TokenModelRemote import com.example.shawarma.data.api.models.TokenModelRemote
import com.example.shawarma.data.api.models.UserModelRemote
import com.example.shawarma.data.api.models.toUserModelRemote import com.example.shawarma.data.api.models.toUserModelRemote
import com.example.shawarma.data.models.UserModel import com.example.shawarma.data.models.UserModel
import javax.inject.Inject import javax.inject.Inject
@ -16,4 +17,8 @@ class RestUserRepository @Inject constructor(
suspend fun getToken(user: UserModel): TokenModelRemote { suspend fun getToken(user: UserModel): TokenModelRemote {
return service.getToken(user.toUserModelRemote()) return service.getToken(user.toUserModelRemote())
} }
suspend fun checkLogin(user: UserModel): UserModelRemote? {
return service.checkLogin(user.toUserModelRemote())
}
} }

View File

@ -13,6 +13,12 @@ interface ProductDao {
@Insert @Insert
suspend fun insert(product: ProductModel) suspend fun insert(product: ProductModel)
suspend fun insertAll(products: List<ProductModel>) {
for (product in products) {
insert(product)
}
}
@Update @Update
suspend fun update(product: ProductModel) suspend fun update(product: ProductModel)
@ -39,4 +45,25 @@ interface ProductDao {
@Query("select * from products") @Query("select * from products")
fun getPagedItems(): PagingSource<Int, ProductModel> fun getPagedItems(): PagingSource<Int, ProductModel>
@Query("delete from products where products.product_old_price is null")
fun deleteAllProducts()
@Query("delete from products where products.product_old_price is not null")
fun deleteAllDiscountProducts()
@Query("delete from products")
fun deleteAllItems()
fun deleteByQuery(query: String) {
if (query == "home") {
deleteAllProducts()
}
if (query == "discounts") {
deleteAllDiscountProducts()
}
if (query == "items") {
deleteAllItems()
}
}
} }

View File

@ -1,8 +1,12 @@
package com.example.shawarma.data.repos package com.example.shawarma.data.repos
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import com.example.shawarma.data.api.MyServerService
import com.example.shawarma.data.api.mediators.ProductRemoteMediator
import com.example.shawarma.data.db.AppDatabase
import com.example.shawarma.data.interfaces.dao.OrderProductDao import com.example.shawarma.data.interfaces.dao.OrderProductDao
import com.example.shawarma.data.interfaces.dao.ProductDao import com.example.shawarma.data.interfaces.dao.ProductDao
import com.example.shawarma.data.models.ProductModel import com.example.shawarma.data.models.ProductModel
@ -10,6 +14,7 @@ import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
class ProductRepository @Inject constructor( class ProductRepository @Inject constructor(
private val database: AppDatabase,
private val productDao: ProductDao, private val productDao: ProductDao,
private val orderProductDao: OrderProductDao private val orderProductDao: OrderProductDao
) { ) {
@ -23,32 +28,27 @@ class ProductRepository @Inject constructor(
orderProductDao.deleteByProductId(product.id!!) orderProductDao.deleteByProductId(product.id!!)
return productDao.delete(product) return productDao.delete(product)
} }
/*fun getAll(): Flow<List<ProductModel>> {
return productDao.getAll()
}
fun getDiscounts(): Flow<List<ProductModel>> {
return productDao.getDiscounts()
}
fun getItems(): Flow<List<ProductModel>> {
return productDao.getItems()
}*/
fun getById(id: Int): Flow<ProductModel> { fun getById(id: Int): Flow<ProductModel> {
return productDao.getById(id) return productDao.getById(id)
} }
fun getAllProductsPaged(): Flow<PagingData<ProductModel>> = Pager( @OptIn(ExperimentalPagingApi::class)
fun getAllProductsPaged(token: String): Flow<PagingData<ProductModel>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 6, pageSize = 6,
enablePlaceholders = false enablePlaceholders = false
), ),
pagingSourceFactory = productDao::getPaged pagingSourceFactory = productDao::getPaged,
remoteMediator = ProductRemoteMediator(database = database, serverService = MyServerService.getInstance(), query = "home", token = token)
).flow ).flow
fun getAllDiscountsPaged(): Flow<PagingData<ProductModel>> = Pager( @OptIn(ExperimentalPagingApi::class)
fun getAllDiscountsPaged(token: String): Flow<PagingData<ProductModel>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 6, pageSize = 6,
enablePlaceholders = false enablePlaceholders = false
), ),
pagingSourceFactory = productDao::getPagedDiscounts pagingSourceFactory = productDao::getPagedDiscounts,
remoteMediator = ProductRemoteMediator(database = database, serverService = MyServerService.getInstance(), query = "discounts", token = token)
).flow ).flow
fun getAllItemsPaged(): Flow<PagingData<ProductModel>> = Pager( fun getAllItemsPaged(): Flow<PagingData<ProductModel>> = Pager(

View File

@ -59,6 +59,13 @@ fun AuthorizationCard(navHostController: NavHostController) {
if (userViewModel.token.observeAsState().value != null) { if (userViewModel.token.observeAsState().value != null) {
preferencesManager.saveData("token", userViewModel.token.value.toString()) preferencesManager.saveData("token", userViewModel.token.value.toString())
if (login.value.text == "admin") {
preferencesManager.saveData("user_role", "ADMIN")
}
else {
preferencesManager.saveData("user_role", "USER")
}
navHostController.navigate(ScreenPaths.home.name) { navHostController.navigate(ScreenPaths.home.name) {
popUpTo(ScreenPaths.authorization.name) { popUpTo(ScreenPaths.authorization.name) {
inclusive = true inclusive = true

View File

@ -58,9 +58,13 @@ fun DiscountScreen() {
@Composable @Composable
fun DiscountList(){ fun DiscountList(){
val preferencesManager = PreferencesManager(LocalContext.current)
val searchToken = preferencesManager.getData("token", "")
val homeViewModel: HomeViewModel = hiltViewModel<HomeViewModel>() val homeViewModel: HomeViewModel = hiltViewModel<HomeViewModel>()
val productsListUiState = homeViewModel.discountListUiState.collectAsLazyPagingItems()
val productsListUiState = homeViewModel.getDiscountList(searchToken).collectAsLazyPagingItems()
Box( Box(
modifier = Modifier modifier = Modifier

View File

@ -57,9 +57,13 @@ fun HomeScreen() {
@Composable @Composable
fun HomeList(){ fun HomeList(){
val preferencesManager = PreferencesManager(LocalContext.current)
val searchToken = preferencesManager.getData("token", "")
val homeViewModel: HomeViewModel = hiltViewModel<HomeViewModel>() val homeViewModel: HomeViewModel = hiltViewModel<HomeViewModel>()
val productsListUiState = homeViewModel.productListUiState.collectAsLazyPagingItems()
val productsListUiState = homeViewModel.getProductList(searchToken).collectAsLazyPagingItems()
Box( Box(

View File

@ -14,6 +14,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
@ -24,9 +25,13 @@ class HomeViewModel @Inject constructor(
private val orderProductRepository: OrderProductRepository private val orderProductRepository: OrderProductRepository
) : ViewModel() { ) : ViewModel() {
val productListUiState: Flow<PagingData<ProductModel>> = productRepository.getAllProductsPaged() fun getProductList(token:String): Flow<PagingData<ProductModel>> {
return productRepository.getAllProductsPaged(token)
}
val discountListUiState: Flow<PagingData<ProductModel>> = productRepository.getAllDiscountsPaged() fun getDiscountList(token: String): Flow<PagingData<ProductModel>> {
return productRepository.getAllDiscountsPaged(token)
}
fun addProductToCart(productId: Int, userId: String) { fun addProductToCart(productId: Int, userId: String) {
@ -36,7 +41,10 @@ class HomeViewModel @Inject constructor(
val product = productRepository.getById(productId).first() val product = productRepository.getById(productId).first()
val order = orderRepository.getUnpaidByUser(userId.toInt()).first() val order = orderRepository.getUnpaidByUser(userId.toInt()).first()
if (order == null) { if (order == null) {
val newOrderId = orderRepository.insert(OrderModel(null, OrderStatus.Неоплачено.name, userId.toInt(), Date())) val calendar: Calendar = Calendar.getInstance()
calendar.time = Date()
calendar.add(Calendar.HOUR_OF_DAY, 4)
val newOrderId = orderRepository.insert(OrderModel(null, OrderStatus.Неоплачено.name, userId.toInt(),calendar.time))
orderProductRepository.insert(OrderProductModel(newOrderId.toInt(), productId, 1, product.price)) orderProductRepository.insert(OrderProductModel(newOrderId.toInt(), productId, 1, product.price))
} }
else { else {

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.shawarma.data.models.OrderStatus import com.example.shawarma.data.models.OrderStatus
import com.example.shawarma.data.models.OrderWithProducts import com.example.shawarma.data.models.OrderWithProducts
import com.example.shawarma.data.repos.OrderProductRepository
import com.example.shawarma.data.repos.OrderRepository import com.example.shawarma.data.repos.OrderRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -14,8 +13,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class OrdersViewModel @Inject constructor( class OrdersViewModel @Inject constructor(
private val orderRepository: OrderRepository, private val orderRepository: OrderRepository
private val orderProductRepository: OrderProductRepository
) : ViewModel() { ) : ViewModel() {
private val _preparingOrders = MutableLiveData<List<OrderWithProducts>>() private val _preparingOrders = MutableLiveData<List<OrderWithProducts>>()
private val _preparedOrders = MutableLiveData<List<OrderWithProducts>>() private val _preparedOrders = MutableLiveData<List<OrderWithProducts>>()

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.shawarma.data.api.repos.RestUserRepository import com.example.shawarma.data.api.repos.RestUserRepository
import com.example.shawarma.data.models.UserModel import com.example.shawarma.data.models.UserModel
import com.example.shawarma.data.repos.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -15,13 +14,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class UserViewModel @Inject constructor( class UserViewModel @Inject constructor(
private val userRepository: UserRepository,
private val restUserRepository: RestUserRepository private val restUserRepository: RestUserRepository
) : ViewModel() { ) : ViewModel() {
private val _userModel = MutableLiveData<UserModel?>()
val userModel: LiveData<UserModel?>
get() = _userModel
private val _token = MutableLiveData<String?>() private val _token = MutableLiveData<String?>()
val token: LiveData<String?> val token: LiveData<String?>
get() = _token get() = _token
@ -33,7 +27,7 @@ class UserViewModel @Inject constructor(
fun login(login: String, password: String){ fun login(login: String, password: String){
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val token_response = restUserRepository.getToken(UserModel(id = null, login = login, password = password, role = "USER")) val token_response = restUserRepository.getToken(UserModel(id = null, login = login, password = password, role = ""))
if (token_response.access_token.isNotEmpty()) { if (token_response.access_token.isNotEmpty()) {
_token.postValue(token_response.access_token) _token.postValue(token_response.access_token)
_authorizationState.postValue(true) _authorizationState.postValue(true)
@ -46,14 +40,12 @@ class UserViewModel @Inject constructor(
} }
} }
} }
fun calmAuthorizationState() { fun calmAuthorizationState() {
_authorizationState.postValue(null) _authorizationState.postValue(null)
} }
private val _registrationState = MutableLiveData<Boolean?>() private val _registrationState = MutableLiveData<Boolean>()
val registrationState: LiveData<Boolean?> val registrationState: LiveData<Boolean>
get() = _registrationState get() = _registrationState
fun register(login: String, password: String, passwordRepeat: String) { fun register(login: String, password: String, passwordRepeat: String) {
@ -72,25 +64,25 @@ class UserViewModel @Inject constructor(
} }
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
userRepository.login(login, password).collect() { user -> val user_response = restUserRepository.checkLogin(UserModel(null, login, password, ""))
if (user == null) { if (user_response != null) {
if (user_response.login.isNullOrEmpty()) {
if (login == "admin" && password == "admin") { if (login == "admin" && password == "admin") {
restUserRepository.insert(UserModel(null, login, password, "ADMIN")) restUserRepository.insert(UserModel(null, login, password, "ADMIN"))
_registrationState.postValue(true) _registrationState.postValue(true)
} } else {
else {
restUserRepository.insert(UserModel(null, login, password, "USER")) restUserRepository.insert(UserModel(null, login, password, "USER"))
_registrationState.postValue(true) _registrationState.postValue(true)
} }
} } else {
else { if (registrationState.value != true) {
_registrationState.postValue(false) _registrationState.postValue(false)
}
} }
} }
} }
} }
} }
fun calmRegistrationState() { fun calmRegistrationState() {
_registrationState.postValue(null) _registrationState.postValue(null)
} }