This commit is contained in:
zyzf 2023-12-04 15:39:21 +04:00
parent ef95904c6f
commit 702d8972bd
12 changed files with 313 additions and 152 deletions

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

View File

@ -75,6 +75,8 @@ dependencies {
implementation("androidx.compose.ui:ui-graphics:1.6.0-beta02") implementation("androidx.compose.ui:ui-graphics:1.6.0-beta02")
implementation("androidx.compose.ui:ui-tooling-preview: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.material3:material3:1.1.2")
implementation("androidx.compose.material:material:1.5.4")
implementation("androidx.paging:paging-compose:3.2.1")
// Room // Room
val room_version = "2.6.1" val room_version = "2.6.1"

View File

@ -27,5 +27,6 @@ class AppDataContainer(private val context: Context) : AppContainer {
companion object { companion object {
const val TIMEOUT = 5000L const val TIMEOUT = 5000L
const val LIMIT = 10
} }
} }

View File

@ -1,5 +1,6 @@
package com.zyzf.coffeepreorder.database.dao package com.zyzf.coffeepreorder.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Query import androidx.room.Query
@ -10,10 +11,10 @@ import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CoffeeDao { interface CoffeeDao {
@Query("select * from coffee order by name collate nocase asc") @Query("select * from coffee order by name collate nocase asc")
fun getAll(): Flow<List<Coffee>> fun getAll(): PagingSource<Int, Coffee>
@Query("select * from coffee where cart_id is not null and count > 0 order by name collate nocase asc") @Query("select * from coffee where cart_id is not null and count > 0 order by name collate nocase asc")
fun getAllInCart(): Flow<List<Coffee>> fun getAllInCart(): PagingSource<Int, Coffee>
@Query("select sum(cost) from coffee where cart_id is not null and count > 0") @Query("select sum(cost) from coffee where cart_id is not null and count > 0")
fun getSumInCart(): Double fun getSumInCart(): Double

View File

@ -17,7 +17,7 @@ import androidx.room.PrimaryKey
]) ])
data class Coffee( data class Coffee(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
var name: String, var name: String,
@ColumnInfo(name = "cost") @ColumnInfo(name = "cost")
@ -36,7 +36,7 @@ data class Coffee(
ingredients: String, ingredients: String,
cartId: Int?, cartId: Int?,
count: Int? count: Int?
) : this(null, name, cost, ingredients, null, 0) ) : this(0, name, cost, ingredients, null, 0)
companion object { companion object {
fun getCoffee(index: Int = 0): Coffee { fun getCoffee(index: Int = 0): Coffee {

View File

@ -1,12 +1,13 @@
package com.zyzf.coffeepreorder.database.repository package com.zyzf.coffeepreorder.database.repository
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface CoffeeRepository { interface CoffeeRepository {
fun getAll(): Flow<List<Coffee>> fun getAll(): Flow<PagingData<Coffee>>
fun getAllInCart(): Flow<List<Coffee>> fun getAllInCart(): Flow<PagingData<Coffee>>
fun getSumInCart(): Double fun getSumInCart(): Double
suspend fun getByUid(uid: Int): CoffeeWithCart? suspend fun getByUid(uid: Int): CoffeeWithCart?
suspend fun insert(name: String, cost: Double, ingredients: String): Long suspend fun insert(name: String, cost: Double, ingredients: String): Long

View File

@ -1,13 +1,29 @@
package com.zyzf.coffeepreorder.database.repository 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.dao.CoffeeDao
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.CoffeeWithCart import com.zyzf.coffeepreorder.database.model.CoffeeWithCart
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository { class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository {
override fun getAll(): Flow<List<Coffee>> = coffeeDao.getAll() override fun getAll(): Flow<PagingData<Coffee>> = Pager(
override fun getAllInCart(): Flow<List<Coffee>> = coffeeDao.getAllInCart() config = PagingConfig(
pageSize = AppDataContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = coffeeDao::getAll
).flow
override fun getAllInCart(): Flow<PagingData<Coffee>> = Pager(
config = PagingConfig(
pageSize = AppDataContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = coffeeDao::getAllInCart
).flow
override fun getSumInCart(): Double = coffeeDao.getSumInCart() override fun getSumInCart(): Double = coffeeDao.getSumInCart()
override suspend fun getByUid(uid: Int): CoffeeWithCart? = coffeeDao.getByUid(uid) 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) override suspend fun insert(name: String, cost: Double, ingredients: String): Long = coffeeDao.insert(name, cost, ingredients)

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
import com.zyzf.coffeepreorder.CoffeeApplication import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.ui.cart.CartViewModel
import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel
object AppViewModelProvider { object AppViewModelProvider {
@ -12,6 +13,9 @@ object AppViewModelProvider {
initializer { initializer {
CoffeeListViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository) CoffeeListViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository)
} }
initializer {
CartViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository)
}
} }
} }

View File

@ -1,5 +1,6 @@
package com.zyzf.coffeepreorder.ui.cart package com.zyzf.coffeepreorder.ui.cart
import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box 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.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Clear 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.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController 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.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee 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.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@ -57,88 +75,153 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class, ExperimentalMaterialApi::class)
@Composable @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) } val openDialog = remember { mutableStateOf(false) }
var coffee by remember { mutableStateOf(Coffee("", 0.0, "", null, 0)) } val coffee = remember { mutableStateOf(Coffee.getCoffee()) }
val context = LocalContext.current var refreshing by remember { mutableStateOf(false) }
val itemsList = remember { mutableStateListOf<Coffee>() } fun refresh() = coroutineScope.launch {
LaunchedEffect(Unit) { refreshing = true
withContext(Dispatchers.IO) { coffeeListUiState.refresh()
AppDatabase.getInstance(context).coffeeDao().getAllInCart().collect { data -> refreshing = false
itemsList.clear() }
itemsList.addAll(data) 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<Coffee>,
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 -> @Composable
Row(modifier = Modifier private fun CartListItem (
.fillMaxWidth() coffee: Coffee,
.heightIn(max = 140.dp) modifier: Modifier = Modifier,
.padding(bottom = 10.dp, top = 10.dp), onDeleteFromCartClick: (coffee: Coffee) -> Unit
horizontalArrangement = Arrangement.SpaceAround) { ) {
AsyncImage( Row(modifier = Modifier
model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + currentCoffee.uid +".png") .fillMaxWidth()
.crossfade(true).build(), .heightIn(max = 140.dp)
error = painterResource(R.drawable.ic_broken_image), .padding(bottom = 10.dp, top = 10.dp),
placeholder = painterResource(R.drawable.loading_img), horizontalArrangement = Arrangement.SpaceAround) {
contentDescription = "Кофе", AsyncImage(
contentScale = ContentScale.Crop, model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png")
modifier = Modifier .crossfade(true).build(),
.size(100.dp) error = painterResource(R.drawable.ic_broken_image),
.clip(RoundedCornerShape(50.dp)) placeholder = painterResource(R.drawable.loading_img),
) contentDescription = "Кофе",
Column( contentScale = ContentScale.Crop,
Modifier modifier = Modifier
.weight(2f) .size(100.dp)
.padding(start = 20.dp)) { .clip(RoundedCornerShape(50.dp))
val currentCoffeeName: String = if (currentCoffee.count > 1) { )
currentCoffee.name + " x" + currentCoffee.count.toString() Column(
} else { Modifier
currentCoffee.name .weight(2f)
} .padding(start = 20.dp)) {
Text(text = currentCoffeeName, fontSize = 25.sp) val currentCoffeeName: String = if (coffee.count > 1) {
Text(text = String.format("%.2f", currentCoffee.cost), fontSize = 20.sp) coffee.name + " x" + coffee.count.toString()
Text(text = currentCoffee.ingredients, fontSize = 15.sp) } else {
Row( coffee.name
Modifier }
.fillMaxWidth() Text(text = currentCoffeeName, fontSize = 25.sp)
.padding(top = 5.dp)) { Text(text = String.format("%.2f", coffee.cost), fontSize = 20.sp)
Button( Text(text = coffee.ingredients, fontSize = 15.sp)
onClick = { coffee = currentCoffee; openDialog.value = true }, Row(
shape = CircleShape, Modifier
modifier = Modifier.padding(start = 10.dp, end = 10.dp), .fillMaxWidth()
colors = ButtonDefaults.buttonColors( .padding(top = 5.dp)) {
containerColor = MaterialTheme.colorScheme.errorContainer, Button(
contentColor = MaterialTheme.colorScheme.error onClick = { onDeleteFromCartClick(coffee) },
) shape = CircleShape,
) { modifier = Modifier.padding(start = 10.dp, end = 10.dp),
Icon( colors = ButtonDefaults.buttonColors(
imageVector = Icons.Default.Clear, containerColor = MaterialTheme.colorScheme.errorContainer,
contentDescription = "Favorite", contentColor = MaterialTheme.colorScheme.error
modifier = Modifier.size(20.dp) )
) ) {
Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Icon(
Text(text = "Удалить из корзины") 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) }, @Composable
icon = { Icon(Icons.Filled.Add, "Сделать заказ") }, private fun DeleteFromCartAlertDialog(
text = { Text(text = "Сделать заказ") }, modifier: Modifier = Modifier,
modifier = Modifier.align(alignment = Alignment.BottomEnd).padding(all = 20.dp), openDialog: MutableState<Boolean>,
containerColor = MaterialTheme.colorScheme.primaryContainer, onConfirmClick: () -> Unit
contentColor = MaterialTheme.colorScheme.primary ) {
)
}
if (openDialog.value) { if (openDialog.value) {
AlertDialog( AlertDialog(
icon = { icon = {
@ -156,12 +239,7 @@ fun Cart(navController: NavController?) {
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
GlobalScope.launch (Dispatchers.Main) { onConfirmClick()
coffee.uid?.let {
AppDatabase.getInstance(context).cartDao().deleteCoffee(it, 1)
}
}
openDialog.value = false openDialog.value = false
} }
) { ) {

View File

@ -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<PagingData<Coffee>> = coffeeRepository.getAllInCart()
suspend fun deleteCoffeeFromCart(coffee: Coffee) {
cartRepository.deleteCoffee(coffee.uid, 1)
}
}

View File

@ -21,12 +21,17 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions 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.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Create 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.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -51,6 +56,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel 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.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.ui.AppViewModelProvider import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable @Composable
fun CoffeeList( fun CoffeeList(
viewModel: CoffeeListViewModel = viewModel(factory = AppViewModelProvider.Factory) viewModel: CoffeeListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val coffeeListUiState = viewModel.coffeeListUiState.collectAsState() val coffeeListUiState = viewModel.coffeeListUiState.collectAsLazyPagingItems()
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState()
val openDialog = remember { mutableStateOf(false) } val openDialog = remember { mutableStateOf(false) }
val coffee = remember { mutableStateOf(Coffee.getCoffee()) } 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( val photoPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia() contract = ActivityResultContracts.PickVisualMedia()
) { ) {
if (it != null) { if (it != null) {
Log.d("PhotoPicker", "Selected URI: $it") Log.d("PhotoPicker", "Selected URI: $it")
viewModel.imageUri = it imageUri = it
} else { } else {
Log.d("PhotoPicker", "No media selected") Log.d("PhotoPicker", "No media selected")
} }
@ -111,48 +132,51 @@ fun CoffeeList(
} }
} }
) { innerPadding -> ) { innerPadding ->
coffeeListUiState.value.coffeeList.let { PullRefreshIndicator(
CoffeeList( refreshing, state,
modifier = Modifier Modifier
.padding(innerPadding) .zIndex(100f)
.fillMaxSize(), )
coffeeList = it, CoffeeList(
onAddToCartClick = { coffeeUid: Int -> modifier = Modifier
coroutineScope.launch { .padding(innerPadding)
viewModel.addCoffeeToCart(coffeeUid = coffeeUid) .fillMaxSize(),
} coffeeList = coffeeListUiState,
}, onAddToCartClick = { coffeeUid: Int ->
onEditClick = { curcoffee: Coffee ->
coroutineScope.launch {
coffee.value = curcoffee
openDialog.value = true
}
}
)
}
AddEditModalBottomSheet(
coffee = coffee,
sheetState = sheetState,
openDialog = openDialog,
onAddClick = { coffee: Coffee, context: Context ->
coroutineScope.launch { coroutineScope.launch {
viewModel.createCoffee(coffee, context) viewModel.addCoffeeToCart(coffeeUid = coffeeUid)
} }
}, },
onEditClick = { coffee: Coffee, context: Context -> onEditClick = { curcoffee: Coffee ->
coroutineScope.launch { 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) @OptIn(ExperimentalMaterial3Api::class)
@ -269,14 +293,21 @@ private fun AddEditModalBottomSheet(
@Composable @Composable
private fun CoffeeList( private fun CoffeeList(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
coffeeList: List<Coffee>, coffeeList: LazyPagingItems<Coffee>,
onAddToCartClick: (coffeeUid: Int) -> Unit, onAddToCartClick: (coffeeUid: Int) -> Unit,
onEditClick: (coffee: Coffee) -> Unit onEditClick: (coffee: Coffee) -> Unit
) { ) {
LazyColumn( LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally) { horizontalAlignment = Alignment.CenterHorizontally) {
items(items = coffeeList, key = { it.uid!! }) { coffee -> items(
CoffeeListItem(coffee = coffee, onAddToCartClick = onAddToCartClick, onEditClick = onEditClick) 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)) { .padding(top = 5.dp)) {
Button( Button(
onClick = { onClick = {
coffee.uid?.let { onAddToCartClick(it) } coffee.uid.let { onAddToCartClick(it) }
}, },
shape = CircleShape, shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f) 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 = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
@ -359,7 +391,29 @@ fun CoffeeListPreview() {
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
CoffeeList( 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<Coffee>()
).collectAsLazyPagingItems(),
onAddToCartClick = {}, onAddToCartClick = {},
onEditClick = {} onEditClick = {}
) )

View File

@ -6,24 +6,19 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.StrictMode import android.os.StrictMode
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.paging.PagingData
import com.jcraft.jsch.Channel import com.jcraft.jsch.Channel
import com.jcraft.jsch.ChannelSftp import com.jcraft.jsch.ChannelSftp
import com.jcraft.jsch.JSch import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException import com.jcraft.jsch.JSchException
import com.jcraft.jsch.Session import com.jcraft.jsch.Session
import com.jcraft.jsch.SftpException 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.Cart
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.repository.CartRepository import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.CoffeeRepository import com.zyzf.coffeepreorder.database.repository.CoffeeRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
@ -34,22 +29,15 @@ class CoffeeListViewModel(
private val coffeeRepository: CoffeeRepository, private val coffeeRepository: CoffeeRepository,
private val cartRepository: CartRepository private val cartRepository: CartRepository
) : ViewModel() { ) : ViewModel() {
val coffeeListUiState: StateFlow<CoffeeListUiState> = coffeeRepository.getAll().map { val coffeeListUiState: Flow<PagingData<Coffee>> = coffeeRepository.getAll()
CoffeeListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = CoffeeListUiState()
)
var imageUri: Any? = R.drawable.img
suspend fun addCoffeeToCart(coffeeUid: Int) { suspend fun addCoffeeToCart(coffeeUid: Int) {
val cart: Cart = cartRepository.get() val cart: Cart = cartRepository.get()
cart.uid?.let { cartRepository.insertCoffee(it, coffeeUid, 1) } 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 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 bitmap = BitmapFactory.decodeStream(inputStream)
val f = File(context.cacheDir, "coffee_image_$newCoffee.png") val f = File(context.cacheDir, "coffee_image_$newCoffee.png")
@ -66,9 +54,9 @@ class CoffeeListViewModel(
copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images") 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 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 bitmap = BitmapFactory.decodeStream(inputStream)
val f = File(context.cacheDir, "coffee_image_$editedCoffee.png") val f = File(context.cacheDir, "coffee_image_$editedCoffee.png")
@ -89,8 +77,6 @@ class CoffeeListViewModel(
} }
} }
data class CoffeeListUiState(val coffeeList: List<Coffee> = listOf())
const val REMOTE_HOST = "109.197.199.134" const val REMOTE_HOST = "109.197.199.134"
const val USERNAME = "zyzf" const val USERNAME = "zyzf"
const val PASSWORD = "250303Zyzf-d-grad" const val PASSWORD = "250303Zyzf-d-grad"