From 702d8972bd8b28cf49dfa11d53b8796ac8d7c3c2 Mon Sep 17 00:00:00 2001 From: zyzf Date: Mon, 4 Dec 2023 15:39:21 +0400 Subject: [PATCH] final --- .idea/misc.xml | 1 - app/build.gradle.kts | 2 + .../coffeepreorder/database/AppContainer.kt | 1 + .../coffeepreorder/database/dao/CoffeeDao.kt | 5 +- .../coffeepreorder/database/model/Coffee.kt | 4 +- .../database/repository/CoffeeRepository.kt | 5 +- .../repository/OfflineCoffeeRepository.kt | 20 +- .../coffeepreorder/ui/AppViewModelProvider.kt | 4 + .../com/zyzf/coffeepreorder/ui/cart/Cart.kt | 236 ++++++++++++------ .../coffeepreorder/ui/cart/CartViewModel.kt | 19 ++ .../coffeepreorder/ui/coffee/CoffeeList.kt | 140 +++++++---- .../ui/coffee/CoffeeListViewModel.kt | 28 +-- 12 files changed, 313 insertions(+), 152 deletions(-) create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/ui/cart/CartViewModel.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b24086d..f430fa4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -75,6 +75,8 @@ dependencies { implementation("androidx.compose.ui:ui-graphics:1.6.0-beta02") implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta02") implementation("androidx.compose.material3:material3:1.1.2") + implementation("androidx.compose.material:material:1.5.4") + implementation("androidx.paging:paging-compose:3.2.1") // Room val room_version = "2.6.1" diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt index 5e7cccc..af07db1 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt @@ -27,5 +27,6 @@ class AppDataContainer(private val context: Context) : AppContainer { companion object { const val TIMEOUT = 5000L + const val LIMIT = 10 } } \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt index 6b18fdd..60a47a0 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt @@ -1,5 +1,6 @@ package com.zyzf.coffeepreorder.database.dao +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete import androidx.room.Query @@ -10,10 +11,10 @@ import kotlinx.coroutines.flow.Flow @Dao interface CoffeeDao { @Query("select * from coffee order by name collate nocase asc") - fun getAll(): Flow> + fun getAll(): PagingSource @Query("select * from coffee where cart_id is not null and count > 0 order by name collate nocase asc") - fun getAllInCart(): Flow> + fun getAllInCart(): PagingSource @Query("select sum(cost) from coffee where cart_id is not null and count > 0") fun getSumInCart(): Double diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt index b12ea3b..a9eaa0f 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt @@ -17,7 +17,7 @@ import androidx.room.PrimaryKey ]) data class Coffee( @PrimaryKey(autoGenerate = true) - val uid: Int?, + val uid: Int = 0, @ColumnInfo(name = "name") var name: String, @ColumnInfo(name = "cost") @@ -36,7 +36,7 @@ data class Coffee( ingredients: String, cartId: Int?, count: Int? - ) : this(null, name, cost, ingredients, null, 0) + ) : this(0, name, cost, ingredients, null, 0) companion object { fun getCoffee(index: Int = 0): Coffee { diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt index 0d40a16..82bc299 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt @@ -1,12 +1,13 @@ package com.zyzf.coffeepreorder.database.repository +import androidx.paging.PagingData import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.CoffeeWithCart import kotlinx.coroutines.flow.Flow interface CoffeeRepository { - fun getAll(): Flow> - fun getAllInCart(): Flow> + fun getAll(): Flow> + fun getAllInCart(): Flow> fun getSumInCart(): Double suspend fun getByUid(uid: Int): CoffeeWithCart? suspend fun insert(name: String, cost: Double, ingredients: String): Long diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt index 9ca235c..93d8dd0 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt @@ -1,13 +1,29 @@ package com.zyzf.coffeepreorder.database.repository +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.zyzf.coffeepreorder.database.AppDataContainer import com.zyzf.coffeepreorder.database.dao.CoffeeDao import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.CoffeeWithCart import kotlinx.coroutines.flow.Flow class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository { - override fun getAll(): Flow> = coffeeDao.getAll() - override fun getAllInCart(): Flow> = coffeeDao.getAllInCart() + override fun getAll(): Flow> = Pager( + config = PagingConfig( + pageSize = AppDataContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = coffeeDao::getAll + ).flow + override fun getAllInCart(): Flow> = Pager( + config = PagingConfig( + pageSize = AppDataContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = coffeeDao::getAllInCart + ).flow override fun getSumInCart(): Double = coffeeDao.getSumInCart() override suspend fun getByUid(uid: Int): CoffeeWithCart? = coffeeDao.getByUid(uid) override suspend fun insert(name: String, cost: Double, ingredients: String): Long = coffeeDao.insert(name, cost, ingredients) diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt index 3d470f4..4658d47 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.zyzf.coffeepreorder.CoffeeApplication +import com.zyzf.coffeepreorder.ui.cart.CartViewModel import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel object AppViewModelProvider { @@ -12,6 +13,9 @@ object AppViewModelProvider { initializer { CoffeeListViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository) } + initializer { + CartViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository) + } } } diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt index d93f1d3..4bc8352 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt @@ -1,5 +1,6 @@ package com.zyzf.coffeepreorder.ui.cart +import android.content.Context import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -15,24 +16,32 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExtendedFloatingActionButton +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.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -43,12 +52,21 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey import coil.compose.AsyncImage import coil.request.ImageRequest import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.ui.AppViewModelProvider +import com.zyzf.coffeepreorder.ui.coffee.CoffeeList +import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import kotlinx.coroutines.DelicateCoroutinesApi @@ -57,88 +75,153 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -@OptIn(DelicateCoroutinesApi::class) +@OptIn(DelicateCoroutinesApi::class, ExperimentalMaterialApi::class) @Composable -fun Cart(navController: NavController?) { +fun Cart( + navController: NavController?, + viewModel: CartViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val coffeeListUiState = viewModel.coffeeListUiState.collectAsLazyPagingItems() val openDialog = remember { mutableStateOf(false) } - var coffee by remember { mutableStateOf(Coffee("", 0.0, "", null, 0)) } - val context = LocalContext.current - val itemsList = remember { mutableStateListOf() } - LaunchedEffect(Unit) { - withContext(Dispatchers.IO) { - AppDatabase.getInstance(context).coffeeDao().getAllInCart().collect { data -> - itemsList.clear() - itemsList.addAll(data) + val coffee = remember { mutableStateOf(Coffee.getCoffee()) } + var refreshing by remember { mutableStateOf(false) } + fun refresh() = coroutineScope.launch { + refreshing = true + coffeeListUiState.refresh() + refreshing = false + } + val state = rememberPullRefreshState(refreshing, ::refresh) + Scaffold( + topBar = {}, + floatingActionButton = { + ExtendedFloatingActionButton( + onClick = { navController?.navigate(Screen.Order.route) }, + icon = { Icon(Icons.Filled.Add, "Сделать заказ") }, + text = { Text(text = "Сделать заказ") }, + modifier = Modifier + .padding(all = 20.dp), + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.primary + ) + } + ) { innerPadding -> + PullRefreshIndicator( + refreshing, state, + Modifier + .zIndex(100f) + ) + CartList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + coffeeList = coffeeListUiState, + onDeleteFromCartClick = {currentCoffee: Coffee -> + coffee.value = currentCoffee + openDialog.value = true + } + ) + } + DeleteFromCartAlertDialog( + openDialog = openDialog, + onConfirmClick = { + coroutineScope.launch { + viewModel.deleteCoffeeFromCart(coffee.value) + } + } + ) +} + +@Composable +private fun CartList( + modifier: Modifier = Modifier, + coffeeList: LazyPagingItems, + onDeleteFromCartClick: (coffee: Coffee) -> Unit +) { + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally) { + items( + count = coffeeList.itemCount, + key = coffeeList.itemKey(), + contentType = coffeeList.itemContentType() + ) {index -> + val coffee = coffeeList[index] + coffee?.let { + CartListItem( + coffee = coffee, + onDeleteFromCartClick = onDeleteFromCartClick + ) } } } - LazyColumn( - horizontalAlignment = Alignment.CenterHorizontally) { - items(itemsList) { currentCoffee -> - Row(modifier = Modifier - .fillMaxWidth() - .heightIn(max = 140.dp) - .padding(bottom = 10.dp, top = 10.dp), - horizontalArrangement = Arrangement.SpaceAround) { - AsyncImage( - model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + currentCoffee.uid +".png") - .crossfade(true).build(), - error = painterResource(R.drawable.ic_broken_image), - placeholder = painterResource(R.drawable.loading_img), - contentDescription = "Кофе", - contentScale = ContentScale.Crop, - modifier = Modifier - .size(100.dp) - .clip(RoundedCornerShape(50.dp)) - ) - Column( - Modifier - .weight(2f) - .padding(start = 20.dp)) { - val currentCoffeeName: String = if (currentCoffee.count > 1) { - currentCoffee.name + " x" + currentCoffee.count.toString() - } else { - currentCoffee.name - } - Text(text = currentCoffeeName, fontSize = 25.sp) - Text(text = String.format("%.2f", currentCoffee.cost), fontSize = 20.sp) - Text(text = currentCoffee.ingredients, fontSize = 15.sp) - Row( - Modifier - .fillMaxWidth() - .padding(top = 5.dp)) { - Button( - onClick = { coffee = currentCoffee; openDialog.value = true }, - shape = CircleShape, - modifier = Modifier.padding(start = 10.dp, end = 10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.error - ) - ) { - Icon( - imageVector = Icons.Default.Clear, - contentDescription = "Favorite", - modifier = Modifier.size(20.dp) - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = "Удалить из корзины") - } - } +} + +@Composable +private fun CartListItem ( + coffee: Coffee, + modifier: Modifier = Modifier, + onDeleteFromCartClick: (coffee: Coffee) -> Unit +) { + Row(modifier = Modifier + .fillMaxWidth() + .heightIn(max = 140.dp) + .padding(bottom = 10.dp, top = 10.dp), + horizontalArrangement = Arrangement.SpaceAround) { + AsyncImage( + model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png") + .crossfade(true).build(), + error = painterResource(R.drawable.ic_broken_image), + placeholder = painterResource(R.drawable.loading_img), + contentDescription = "Кофе", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(100.dp) + .clip(RoundedCornerShape(50.dp)) + ) + Column( + Modifier + .weight(2f) + .padding(start = 20.dp)) { + val currentCoffeeName: String = if (coffee.count > 1) { + coffee.name + " x" + coffee.count.toString() + } else { + coffee.name + } + Text(text = currentCoffeeName, fontSize = 25.sp) + Text(text = String.format("%.2f", coffee.cost), fontSize = 20.sp) + Text(text = coffee.ingredients, fontSize = 15.sp) + Row( + Modifier + .fillMaxWidth() + .padding(top = 5.dp)) { + Button( + onClick = { onDeleteFromCartClick(coffee) }, + shape = CircleShape, + modifier = Modifier.padding(start = 10.dp, end = 10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.error + ) + ) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = "Favorite", + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = "Удалить из корзины") } } } } - Box(modifier = Modifier.fillMaxSize()) { - ExtendedFloatingActionButton( - onClick = { navController?.navigate(Screen.Order.route) }, - icon = { Icon(Icons.Filled.Add, "Сделать заказ") }, - text = { Text(text = "Сделать заказ") }, - modifier = Modifier.align(alignment = Alignment.BottomEnd).padding(all = 20.dp), - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.primary - ) - } +} + +@Composable +private fun DeleteFromCartAlertDialog( + modifier: Modifier = Modifier, + openDialog: MutableState, + onConfirmClick: () -> Unit +) { if (openDialog.value) { AlertDialog( icon = { @@ -156,12 +239,7 @@ fun Cart(navController: NavController?) { confirmButton = { TextButton( onClick = { - GlobalScope.launch (Dispatchers.Main) { - coffee.uid?.let { - AppDatabase.getInstance(context).cartDao().deleteCoffee(it, 1) - } - } - + onConfirmClick() openDialog.value = false } ) { diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/CartViewModel.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/CartViewModel.kt new file mode 100644 index 0000000..c591988 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/CartViewModel.kt @@ -0,0 +1,19 @@ +package com.zyzf.coffeepreorder.ui.cart + +import androidx.lifecycle.ViewModel +import androidx.paging.PagingData +import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.database.repository.CartRepository +import com.zyzf.coffeepreorder.database.repository.CoffeeRepository +import kotlinx.coroutines.flow.Flow + +class CartViewModel( + private val coffeeRepository: CoffeeRepository, + private val cartRepository: CartRepository +) : ViewModel() { + val coffeeListUiState: Flow> = coffeeRepository.getAllInCart() + + suspend fun deleteCoffeeFromCart(coffee: Coffee) { + cartRepository.deleteCoffee(coffee.uid, 1) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt index 3d5109a..204c359 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt @@ -21,12 +21,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.outlined.Create +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -51,6 +56,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale @@ -61,31 +67,46 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey import coil.compose.AsyncImage import coil.request.ImageRequest import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.ui.AppViewModelProvider import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun CoffeeList( viewModel: CoffeeListViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { val coroutineScope = rememberCoroutineScope() - val coffeeListUiState = viewModel.coffeeListUiState.collectAsState() + val coffeeListUiState = viewModel.coffeeListUiState.collectAsLazyPagingItems() val sheetState = rememberModalBottomSheetState() val openDialog = remember { mutableStateOf(false) } val coffee = remember { mutableStateOf(Coffee.getCoffee()) } + var imageUri: Any? by remember { mutableStateOf(R.drawable.img) } + var refreshing by remember { mutableStateOf(false) } + fun refresh() = coroutineScope.launch { + refreshing = true + coffeeListUiState.refresh() + refreshing = false + } + val state = rememberPullRefreshState(refreshing, ::refresh) val photoPicker = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia() ) { if (it != null) { Log.d("PhotoPicker", "Selected URI: $it") - viewModel.imageUri = it + imageUri = it } else { Log.d("PhotoPicker", "No media selected") } @@ -111,48 +132,51 @@ fun CoffeeList( } } ) { innerPadding -> - coffeeListUiState.value.coffeeList.let { - CoffeeList( - modifier = Modifier - .padding(innerPadding) - .fillMaxSize(), - coffeeList = it, - onAddToCartClick = { coffeeUid: Int -> - coroutineScope.launch { - viewModel.addCoffeeToCart(coffeeUid = coffeeUid) - } - }, - onEditClick = { curcoffee: Coffee -> - coroutineScope.launch { - coffee.value = curcoffee - openDialog.value = true - } - } - ) - } - AddEditModalBottomSheet( - coffee = coffee, - sheetState = sheetState, - openDialog = openDialog, - onAddClick = { coffee: Coffee, context: Context -> + PullRefreshIndicator( + refreshing, state, + Modifier + .zIndex(100f) + ) + CoffeeList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + coffeeList = coffeeListUiState, + onAddToCartClick = { coffeeUid: Int -> coroutineScope.launch { - viewModel.createCoffee(coffee, context) + viewModel.addCoffeeToCart(coffeeUid = coffeeUid) } }, - onEditClick = { coffee: Coffee, context: Context -> + onEditClick = { curcoffee: Coffee -> coroutineScope.launch { - viewModel.editCoffee(coffee, context) + coffee.value = curcoffee + openDialog.value = true } - }, - onDeleteClick = { coffee: Coffee -> - coroutineScope.launch { - viewModel.deleteCoffee(coffee) - } - }, - photoPicker = photoPicker, - imageUri = viewModel.imageUri + } ) } + AddEditModalBottomSheet( + coffee = coffee, + sheetState = sheetState, + openDialog = openDialog, + onAddClick = { coffee: Coffee, context: Context -> + coroutineScope.launch { + viewModel.createCoffee(coffee, imageUri as Uri, context) + } + }, + onEditClick = { coffee: Coffee, context: Context -> + coroutineScope.launch { + viewModel.editCoffee(coffee, imageUri as Uri, context) + } + }, + onDeleteClick = { coffee: Coffee -> + coroutineScope.launch { + viewModel.deleteCoffee(coffee) + } + }, + photoPicker = photoPicker, + imageUri = imageUri + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -269,14 +293,21 @@ private fun AddEditModalBottomSheet( @Composable private fun CoffeeList( modifier: Modifier = Modifier, - coffeeList: List, + coffeeList: LazyPagingItems, onAddToCartClick: (coffeeUid: Int) -> Unit, onEditClick: (coffee: Coffee) -> Unit ) { LazyColumn( horizontalAlignment = Alignment.CenterHorizontally) { - items(items = coffeeList, key = { it.uid!! }) { coffee -> - CoffeeListItem(coffee = coffee, onAddToCartClick = onAddToCartClick, onEditClick = onEditClick) + items( + count = coffeeList.itemCount, + key = coffeeList.itemKey(), + contentType = coffeeList.itemContentType() + ) {index -> + val coffee = coffeeList[index] + coffee?.let { + CoffeeListItem(coffee = coffee, onAddToCartClick = onAddToCartClick, onEditClick = onEditClick) + } } } } @@ -319,7 +350,7 @@ private fun CoffeeListItem( .padding(top = 5.dp)) { Button( onClick = { - coffee.uid?.let { onAddToCartClick(it) } + coffee.uid.let { onAddToCartClick(it) } }, shape = CircleShape, modifier = Modifier.fillMaxWidth(fraction = 0.75f) @@ -350,6 +381,7 @@ private fun CoffeeListItem( } } + @Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -359,7 +391,29 @@ fun CoffeeListPreview() { color = MaterialTheme.colorScheme.background ) { CoffeeList( - coffeeList = (1..20).map { i -> Coffee.getCoffee(i) }, + coffeeList = MutableStateFlow( + PagingData.from((1..20).map { i -> Coffee.getCoffee(i) }) + ).collectAsLazyPagingItems(), + onAddToCartClick = {}, + onEditClick = {} + ) + } + } +} + + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun CoffeeEmptyListPreview() { + CoffeePreorderTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + CoffeeList( + coffeeList = MutableStateFlow( + PagingData.empty() + ).collectAsLazyPagingItems(), onAddToCartClick = {}, onEditClick = {} ) diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt index 860dfdb..805dd57 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt @@ -6,24 +6,19 @@ import android.graphics.BitmapFactory import android.net.Uri import android.os.StrictMode import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData import com.jcraft.jsch.Channel import com.jcraft.jsch.ChannelSftp import com.jcraft.jsch.JSch import com.jcraft.jsch.JSchException import com.jcraft.jsch.Session import com.jcraft.jsch.SftpException -import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.database.AppDataContainer import com.zyzf.coffeepreorder.database.model.Cart import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.repository.CartRepository import com.zyzf.coffeepreorder.database.repository.CoffeeRepository import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.io.File @@ -34,22 +29,15 @@ class CoffeeListViewModel( private val coffeeRepository: CoffeeRepository, private val cartRepository: CartRepository ) : ViewModel() { - val coffeeListUiState: StateFlow = coffeeRepository.getAll().map { - CoffeeListUiState(it) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), - initialValue = CoffeeListUiState() - ) - var imageUri: Any? = R.drawable.img + val coffeeListUiState: Flow> = coffeeRepository.getAll() suspend fun addCoffeeToCart(coffeeUid: Int) { val cart: Cart = cartRepository.get() cart.uid?.let { cartRepository.insertCoffee(it, coffeeUid, 1) } } - suspend fun createCoffee(coffee: Coffee, context: Context) { + suspend fun createCoffee(coffee: Coffee, imageUri: Uri, context: Context) { val newCoffee: Long = coffeeRepository.insert(coffee.name, coffee.cost, coffee.ingredients) - val inputStream = context.contentResolver.openInputStream(imageUri as Uri) + val inputStream = context.contentResolver.openInputStream(imageUri) val bitmap = BitmapFactory.decodeStream(inputStream) val f = File(context.cacheDir, "coffee_image_$newCoffee.png") @@ -66,9 +54,9 @@ class CoffeeListViewModel( copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images") } - suspend fun editCoffee(coffee: Coffee, context: Context) { + suspend fun editCoffee(coffee: Coffee, imageUri: Uri, context: Context) { val editedCoffee: Int = coffeeRepository.update(coffee.uid!!, coffee.name, coffee.cost, coffee.ingredients, coffee.cartId, coffee.count)!! - val inputStream = context.contentResolver.openInputStream(imageUri as Uri) + val inputStream = context.contentResolver.openInputStream(imageUri) val bitmap = BitmapFactory.decodeStream(inputStream) val f = File(context.cacheDir, "coffee_image_$editedCoffee.png") @@ -89,8 +77,6 @@ class CoffeeListViewModel( } } -data class CoffeeListUiState(val coffeeList: List = listOf()) - const val REMOTE_HOST = "109.197.199.134" const val USERNAME = "zyzf" const val PASSWORD = "250303Zyzf-d-grad"