init
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package cc.n0th1ng.tripmoney.screens
|
||||
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
@@ -8,14 +11,17 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -27,44 +33,68 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import cc.n0th1ng.tripmoney.R
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import cc.n0th1ng.tripmoney.utils.Icons
|
||||
import cc.n0th1ng.tripmoney.utils.colors
|
||||
|
||||
@Composable
|
||||
fun AddCategoryDialog(onDismiss: () -> Unit, onSave: (Category) -> Unit) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
var icon by remember { mutableStateOf(Icons.entries[0]) }
|
||||
var color by remember { mutableStateOf(colors[0]) }
|
||||
fun AddCategoryDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (Category) -> Unit,
|
||||
categoryToEdit: Category? = null
|
||||
) {
|
||||
var name by remember { mutableStateOf(categoryToEdit?.name ?: "") }
|
||||
var icon by remember { mutableStateOf(categoryToEdit?.icon ?: Icons.entries[0]) }
|
||||
var color by remember { mutableStateOf(categoryToEdit?.color ?: colors[0]) }
|
||||
var isArchived by remember { mutableStateOf(categoryToEdit?.archived ?: false) }
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss, title = { Text("Add new category") }, text = {
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(if (categoryToEdit == null) R.string.add_new_category else R.string.edit_category)) },
|
||||
text = {
|
||||
AlertDialogFill(
|
||||
onTextChange = { newText ->
|
||||
name = newText
|
||||
},
|
||||
onIconChange = { newIcon -> icon = newIcon },
|
||||
onColorChange = { newColor -> color = newColor }
|
||||
onColorChange = { newColor -> color = newColor },
|
||||
onArchivedChange = { newArchived ->
|
||||
isArchived = newArchived
|
||||
},
|
||||
name = name,
|
||||
icon = icon,
|
||||
color = color,
|
||||
isArchived = isArchived
|
||||
)
|
||||
}, confirmButton = {
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
enabled = !name.isEmpty(),
|
||||
onClick = {
|
||||
onSave(
|
||||
Category(
|
||||
name = name,
|
||||
icon = icon,
|
||||
color = color
|
||||
)
|
||||
val categoryToSave = Category(
|
||||
name = name,
|
||||
icon = icon,
|
||||
color = color,
|
||||
archived = isArchived
|
||||
)
|
||||
}) { Text("Save") }
|
||||
onSave(
|
||||
if (categoryToEdit != null) categoryToSave.copy(id = categoryToEdit.id) else categoryToSave
|
||||
)
|
||||
}) { Text(stringResource(R.string.save)) }
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.error),
|
||||
onClick = onDismiss
|
||||
) { Text("close") }
|
||||
Row() {
|
||||
Button(
|
||||
colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.secondary),
|
||||
onClick = onDismiss
|
||||
) { Text(stringResource(R.string.cancel)) }
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,24 +102,26 @@ fun AddCategoryDialog(onDismiss: () -> Unit, onSave: (Category) -> Unit) {
|
||||
fun AlertDialogFill(
|
||||
onTextChange: (String) -> Unit,
|
||||
onIconChange: (Icons) -> Unit,
|
||||
onColorChange: (String) -> Unit
|
||||
onColorChange: (String) -> Unit,
|
||||
onArchivedChange: (Boolean) -> Unit,
|
||||
name: String,
|
||||
icon: Icons,
|
||||
color: String,
|
||||
isArchived: Boolean
|
||||
) {
|
||||
var text by remember { mutableStateOf("") }
|
||||
var iconId by remember { mutableIntStateOf(Icons.entries[0].resource) }
|
||||
var colorHex by remember { mutableStateOf(colors[0]) }
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(30.dp),
|
||||
painter = painterResource(iconId), contentDescription = null,
|
||||
tint = Color(colorHex.toColorInt())
|
||||
painter = painterResource(icon.resource), contentDescription = null,
|
||||
tint = Color(color.toColorInt())
|
||||
)
|
||||
OutlinedTextField(label = { Text("Name") }, value = text, onValueChange = { newText ->
|
||||
text = newText
|
||||
onTextChange(text)
|
||||
OutlinedTextField(label = { Text("Name") }, value = name, onValueChange = { newText ->
|
||||
onTextChange(newText)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,7 +136,6 @@ fun AlertDialogFill(
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
.clickable(onClick = {
|
||||
iconId = icon.resource
|
||||
onIconChange(icon)
|
||||
}),
|
||||
painter = painterResource(icon.resource),
|
||||
@@ -123,8 +154,7 @@ fun AlertDialogFill(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
colorHex = color
|
||||
onColorChange(colorHex)
|
||||
onColorChange(color)
|
||||
})
|
||||
.size(30.dp)
|
||||
.aspectRatio(1f)
|
||||
@@ -132,5 +162,49 @@ fun AlertDialogFill(
|
||||
) {}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Switch(
|
||||
checked = isArchived,
|
||||
onCheckedChange = onArchivedChange
|
||||
)
|
||||
Text(
|
||||
text = "Archived",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewAddCategoryDialog() {
|
||||
TripMoneyTheme {
|
||||
AddCategoryDialog(
|
||||
onDismiss = {},
|
||||
onSave = {})
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewEditCategoryDialog() {
|
||||
TripMoneyTheme {
|
||||
AddCategoryDialog(
|
||||
onDismiss = {},
|
||||
onSave = {},
|
||||
categoryToEdit = Category(
|
||||
0, "Hotel",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.entries.random(),
|
||||
color = colors.random(),
|
||||
archived = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -11,6 +12,7 @@ import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -224,7 +226,7 @@ fun AddExpenseBottomSheet(
|
||||
) {
|
||||
Text(
|
||||
text = datetime.format(DateTimeFormatter.ofPattern("dd.MM HH:mm")),
|
||||
fontSize = 17.sp
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
CategoryButton(
|
||||
@@ -265,6 +267,11 @@ fun AddExpenseBottomSheet(
|
||||
equationResult = evaluate(amount)
|
||||
enableSave = amount.isDoubleTwoDigitsOrEquation() && equationResult > 0
|
||||
},
|
||||
onLongBackspaceClick = {
|
||||
amount = "0.00"
|
||||
equationResult = evaluate(amount)
|
||||
enableSave = false
|
||||
}
|
||||
)
|
||||
|
||||
SaveButton(
|
||||
@@ -394,7 +401,13 @@ fun NoteInput(
|
||||
|
||||
@Composable
|
||||
fun CurrencyButton(modifier: Modifier = Modifier, onClick: () -> Unit, text: String) {
|
||||
Button(onClick = onClick, modifier = modifier, shape = MaterialTheme.shapes.medium) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = ButtonDefaults.buttonColors()
|
||||
.copy(containerColor = MaterialTheme.colorScheme.secondary)
|
||||
) {
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
@@ -402,18 +415,35 @@ fun CurrencyButton(modifier: Modifier = Modifier, onClick: () -> Unit, text: Str
|
||||
@Composable
|
||||
fun CategoryButton(onClick: () -> Unit, category: Category, modifier: Modifier = Modifier) {
|
||||
Button(
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = ButtonDefaults.buttonColors()
|
||||
.copy(containerColor = Color(category.color.toColorInt()), contentColor = Color.Black)
|
||||
.copy(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
) {
|
||||
// Row(modifier = modifier.fillMaxWidth()) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 10.dp),
|
||||
tint = Color(category.color.toColorInt()),
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
// .background(
|
||||
// color = MaterialTheme.colorScheme.prima,
|
||||
// shape = MaterialTheme.shapes.small
|
||||
// )
|
||||
.padding(end = 10.dp),
|
||||
painter = painterResource(category.icon.resource),
|
||||
contentDescription = stringResource(R.string.category),
|
||||
)
|
||||
Text(category.name)
|
||||
Text(
|
||||
text = category.name,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +467,8 @@ fun NumberKeyboard(
|
||||
modifier: Modifier = Modifier,
|
||||
onNumberClick: (String) -> Unit,
|
||||
onBackspaceClick: () -> Unit,
|
||||
onOperatorClick: (String) -> Unit
|
||||
onOperatorClick: (String) -> Unit,
|
||||
onLongBackspaceClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
@@ -455,22 +486,23 @@ fun NumberKeyboard(
|
||||
onClick = onBackspaceClick,
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
containerColor = Color.Transparent,
|
||||
onLongClick = onLongBackspaceClick
|
||||
)
|
||||
|
||||
"+", "/", "-", "*" -> KeyboardButton(
|
||||
text = key,
|
||||
onClick = { onOperatorClick(key) },
|
||||
modifier = Modifier.weight(1f),
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
|
||||
else -> KeyboardButton(
|
||||
text = key,
|
||||
onClick = { onNumberClick(key) },
|
||||
modifier = Modifier.weight(1f),
|
||||
containerColor = MaterialTheme.colorScheme.secondary,
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondary
|
||||
)
|
||||
}
|
||||
@@ -487,26 +519,24 @@ fun KeyboardButton(
|
||||
icon: Painter? = null,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
onLongClick: () -> Unit = {},
|
||||
containerColor: Color = MaterialTheme.colorScheme.primary,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onPrimary
|
||||
) {
|
||||
|
||||
Button(
|
||||
onClick = onClick,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.padding(2.dp)
|
||||
.aspectRatio(2.5f),
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor
|
||||
)
|
||||
) {
|
||||
.aspectRatio(2.5f)
|
||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
.background(containerColor, shape = MaterialTheme.shapes.medium),
|
||||
|
||||
) {
|
||||
when {
|
||||
text != null -> Text(
|
||||
text,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
|
||||
icon != null -> Icon(painter = icon, contentDescription = null)
|
||||
@@ -594,26 +624,31 @@ fun PreviewAddExpenseEnabled() {
|
||||
|
||||
val categoriesToPreview = listOf(
|
||||
Category(
|
||||
1,
|
||||
name = "Hotel",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.HOTEL,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
2,
|
||||
name = "Jedzenie",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.RESTAURANT,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
3,
|
||||
name = "Transport",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.FLIGHT,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
4,
|
||||
name = "Rozrywka",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.ATTRACTION,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
5,
|
||||
name = "Zakupy",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.GROCERIES,
|
||||
color = colors.random()
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import cc.n0th1ng.tripmoney.R.*
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.screens.AddCategoryDialog
|
||||
import cc.n0th1ng.tripmoney.utils.Icons
|
||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import com.composables.icons.materialsymbols.outlined.R
|
||||
|
||||
@@ -37,7 +36,7 @@ fun CategorySelectionDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onCategorySelected: (Category) -> Unit,
|
||||
selected: Category,
|
||||
categories: List<Category>
|
||||
categories: List<Category>,
|
||||
) {
|
||||
val expenseAndCategoryViewModel: ExpenseAndCategoryViewModel = hiltViewModel()
|
||||
val listState = rememberLazyListState()
|
||||
@@ -91,7 +90,7 @@ fun CategorySelectionDialog(
|
||||
contentDescription = stringResource(string.category)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(string.add_new_category), modifier = Modifier.padding(start = 8.dp),
|
||||
text = stringResource(string.add_new), modifier = Modifier.padding(start = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
@@ -47,6 +46,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -58,33 +58,46 @@ import androidx.paging.PagingData
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.AddExpenseBottomSheet
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
import cc.n0th1ng.tripmoney.utils.colors
|
||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel.ExpenseListItemUi
|
||||
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun ListExpenseScreen() {
|
||||
fun ListExpenseScreen(filter: String) {
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||
val tripViewModel: TripViewModel = hiltViewModel()
|
||||
val currentTripId by settingsViewModel.currentTrip.collectAsState()
|
||||
val currentTrip by tripViewModel.getTrip(currentTripId).collectAsState(Trip.DUMMY)
|
||||
val expenseAndCategoryViewModel: ExpenseAndCategoryViewModel = hiltViewModel()
|
||||
val expensesFlow = expenseAndCategoryViewModel.getExpensesWithHeadersPaged(currentTripId)
|
||||
val expensesFlow =
|
||||
expenseAndCategoryViewModel.getExpensesWithHeadersPaged(currentTripId, filter)
|
||||
val isRecalculatingRate by tripViewModel.isRecalculating.collectAsState()
|
||||
|
||||
ListExpenseScreen(
|
||||
expensesFlow = expensesFlow,
|
||||
onSaveExpense = { expenseAndCategoryViewModel.save(it, currentTrip!!) },
|
||||
onDeleteExpense = { expenseAndCategoryViewModel.delete(it) },
|
||||
isRecalculatingRate = isRecalculatingRate
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,7 +107,8 @@ fun ListExpenseScreen() {
|
||||
@Composable
|
||||
fun ListExpenseScreen(
|
||||
expensesFlow: Flow<PagingData<ExpenseListItemUi>>,
|
||||
onSaveExpense: (Expense) -> Unit, onDeleteExpense: (Expense) -> Unit
|
||||
onSaveExpense: (Expense) -> Unit, onDeleteExpense: (Expense) -> Unit,
|
||||
isRecalculatingRate: Boolean
|
||||
) {
|
||||
|
||||
val items = expensesFlow.collectAsLazyPagingItems()
|
||||
@@ -111,58 +125,52 @@ fun ListExpenseScreen(
|
||||
)
|
||||
})
|
||||
{
|
||||
if (items.loadState.refresh == LoadState.Loading) {
|
||||
// Show loading indicator
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = listState
|
||||
) {
|
||||
items(
|
||||
count = items.itemCount,
|
||||
key = items.itemKey { item ->
|
||||
when (item) {
|
||||
is ExpenseListItemUi.Item -> item.expenseDto.expense.id
|
||||
is ExpenseListItemUi.Header -> "header_${item.date}"
|
||||
}
|
||||
Box {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = listState
|
||||
) {
|
||||
items(
|
||||
count = items.itemCount,
|
||||
key = items.itemKey { item ->
|
||||
when (item) {
|
||||
is ExpenseListItemUi.Item -> item.expenseDto.expense.id
|
||||
is ExpenseListItemUi.Header -> "header_${item.date}"
|
||||
}
|
||||
) { index ->
|
||||
}
|
||||
) { index ->
|
||||
|
||||
when (val item = items[index]) {
|
||||
|
||||
is ExpenseListItemUi.Header -> {
|
||||
CustomDivider(
|
||||
date = item.date,
|
||||
sum = item.sum,
|
||||
currency = item.currency
|
||||
)
|
||||
}
|
||||
|
||||
is ExpenseListItemUi.Item -> {
|
||||
SwipeToDeleteExpenseCard(
|
||||
expenseDto = item.expenseDto,
|
||||
onDelete = { expense -> itemToDelete = expense },
|
||||
onClick = { expenseDto ->
|
||||
expenseDtoToEdit = expenseDto
|
||||
showBottomSheet = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
null -> {}
|
||||
when (val item = items[index]) {
|
||||
|
||||
is ExpenseListItemUi.Header -> {
|
||||
CustomDivider(
|
||||
date = item.date,
|
||||
sum = item.sum,
|
||||
currency = item.currency
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
is ExpenseListItemUi.Item -> {
|
||||
SwipeToDeleteExpenseCard(
|
||||
expenseDto = item.expenseDto,
|
||||
onDelete = { expense -> itemToDelete = expense },
|
||||
onClick = { expenseDto ->
|
||||
expenseDtoToEdit = expenseDto
|
||||
showBottomSheet = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
null -> {}
|
||||
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (itemToDelete != null) {
|
||||
DeleteConfirmationDialog(
|
||||
@@ -208,7 +216,7 @@ fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
||||
date.format(
|
||||
DateTimeFormatter.ofPattern("dd EEEE")
|
||||
).toString(),
|
||||
modifier = Modifier.background(Color.White.copy(alpha = 0f)),
|
||||
modifier = Modifier.padding(horizontal = 5.dp).background(Color.White.copy(alpha = 0f)),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Row(
|
||||
@@ -221,8 +229,14 @@ fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
||||
HorizontalDivider(modifier = Modifier.weight(2f))
|
||||
Text(
|
||||
"%.2f %s".format(sum, currency),
|
||||
modifier = Modifier.background(Color.White.copy(alpha = 0f)),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
modifier = Modifier
|
||||
.background(
|
||||
MaterialTheme.colorScheme.tertiaryContainer,
|
||||
shape = MaterialTheme.shapes.small
|
||||
)
|
||||
.padding(5.dp),
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||
}
|
||||
@@ -256,7 +270,7 @@ fun SwipeToDeleteExpenseCard(
|
||||
Modifier
|
||||
.clip(CardDefaults.elevatedShape)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.onError)
|
||||
.background(MaterialTheme.colorScheme.errorContainer)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
@@ -329,7 +343,7 @@ fun ExpenseCard(
|
||||
) {
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors()
|
||||
.copy(containerColor = MaterialTheme.colorScheme.secondaryContainer),
|
||||
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.9f)
|
||||
.height(70.dp)
|
||||
@@ -344,14 +358,29 @@ fun ExpenseCard(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
//TODO
|
||||
// .background(
|
||||
// Brush.horizontalGradient(
|
||||
// colorStops = arrayOf(
|
||||
// 1f to Color(expenseDto.category.color.toColorInt()),
|
||||
// 4f to MaterialTheme.colorScheme.surfaceDim
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(15.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceDim,
|
||||
shape = MaterialTheme.shapes.small
|
||||
)
|
||||
.padding(10.dp),
|
||||
painter = painterResource(expenseDto.category.icon.resource),
|
||||
contentDescription = "Category",
|
||||
tint = Color(expenseDto.category.color.toColorInt())
|
||||
@@ -367,13 +396,13 @@ fun ExpenseCard(
|
||||
Text(
|
||||
text = expenseDto.category.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(0.dp),
|
||||
text = expenseDto.expense.note,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
@@ -382,7 +411,7 @@ fun ExpenseCard(
|
||||
DateTimeFormatter.ofPattern("dd MMM HH:mm")
|
||||
),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -390,7 +419,7 @@ fun ExpenseCard(
|
||||
Text(
|
||||
text = "- %.2f ${expenseDto.expense.currency}".format(expenseDto.expense.amount),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
|
||||
|
||||
)
|
||||
@@ -398,7 +427,7 @@ fun ExpenseCard(
|
||||
Text(
|
||||
text = "≈ %.2f ${expenseDto.trip.currency}".format(expenseDto.expense.convertedAmount()),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
@@ -407,107 +436,125 @@ fun ExpenseCard(
|
||||
}
|
||||
}
|
||||
|
||||
//@RequiresApi(Build.VERSION_CODES.O)
|
||||
//@AllPreviews
|
||||
//@Composable
|
||||
//fun PreviewListExpenseScreen() {
|
||||
// TripMoneyTheme() {
|
||||
// val pagingData = PagingData.from(sampleExpenseDtoWithConvertedAmountList())
|
||||
// ListExpenseScreen(
|
||||
// expensesDtoFlow = MutableStateFlow(pagingData),
|
||||
// onSaveExpense = {},
|
||||
// onDeleteExpense = {},
|
||||
// dailySums = emptyMap()
|
||||
// )
|
||||
//
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@AllPreviews
|
||||
//@Composable
|
||||
//fun PreviewDeleteConfirmationDialog() {
|
||||
// TripMoneyTheme() {
|
||||
// DeleteConfirmationDialog(
|
||||
// onConfirm = {},
|
||||
// onCancel = {})
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//@RequiresApi(Build.VERSION_CODES.O)
|
||||
//private fun sampleExpenseDtoWithConvertedAmountList(): List<ExpenseDto> {
|
||||
// val sampleCategories = listOf(
|
||||
// Category(
|
||||
// name = "Hotel",
|
||||
// icon = cc.n0th1ng.tripmoney.utils.Icons.HOTEL,
|
||||
// color = colors.random()
|
||||
// ),
|
||||
// Category(
|
||||
// name = "Jedzenie",
|
||||
// icon = cc.n0th1ng.tripmoney.utils.Icons.RESTAURANT,
|
||||
// color = colors.random()
|
||||
// ),
|
||||
// Category(
|
||||
// name = "Transport",
|
||||
// icon = cc.n0th1ng.tripmoney.utils.Icons.FLIGHT,
|
||||
// color = colors.random()
|
||||
// ),
|
||||
// Category(
|
||||
// name = "Rozrywka",
|
||||
// icon = cc.n0th1ng.tripmoney.utils.Icons.ATTRACTION,
|
||||
// color = colors.random()
|
||||
// ),
|
||||
// Category(
|
||||
// name = "Zakupy",
|
||||
// icon = cc.n0th1ng.tripmoney.utils.Icons.GROCERIES,
|
||||
// color = colors.random()
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
// val trip = Trip(
|
||||
// id = 1,
|
||||
// name = "Vacation",
|
||||
// currency = "USD",
|
||||
// startDate = LocalDate.parse("2026-01-01")
|
||||
// )
|
||||
//
|
||||
// val startLong = LocalDateTime.now().minusDays(10).toEpochMilli()
|
||||
// val endLong = LocalDateTime.now().toEpochMilli()
|
||||
//
|
||||
// val result: MutableList<ExpenseDto> = mutableListOf()
|
||||
// for (i in 0..15) {
|
||||
// val category = sampleCategories.random()
|
||||
// val datetime = if (i > 4) {
|
||||
// LocalDateTime.ofEpochSecond(
|
||||
// Random.nextLong(startLong, endLong),
|
||||
// 0,
|
||||
// ZoneOffset.UTC
|
||||
// )
|
||||
// } else LocalDateTime.now()
|
||||
//
|
||||
// val expense = Expense(
|
||||
// id = i,
|
||||
// categoryId = category.id,
|
||||
// tripId = 1,
|
||||
// amount = Random.nextDouble(0.1, 300.0),
|
||||
// currency = Currencies.entries.random().name,
|
||||
// note = if (i % 3 == 0) "Some note" else "",
|
||||
// datetime = datetime,
|
||||
// rate = if (Random.nextBoolean()) Random.nextDouble(
|
||||
// 0.1,
|
||||
// 5.0
|
||||
// ) else 1.0
|
||||
// )
|
||||
//
|
||||
//
|
||||
// val expenseDto = ExpenseDto(
|
||||
// expense = expense,
|
||||
// category = category,
|
||||
// trip = trip
|
||||
// )
|
||||
// result.add(
|
||||
// expenseDto
|
||||
// )
|
||||
// }
|
||||
// return result
|
||||
//}
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewListExpenseScreen() {
|
||||
TripMoneyTheme() {
|
||||
val pagingData = PagingData.from(sampleExpenseDtoWithConvertedAmountList())
|
||||
ListExpenseScreen(
|
||||
expensesFlow = MutableStateFlow(pagingData),
|
||||
onSaveExpense = {},
|
||||
onDeleteExpense = {},
|
||||
true
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewDeleteConfirmationDialog() {
|
||||
TripMoneyTheme() {
|
||||
DeleteConfirmationDialog(
|
||||
onConfirm = {},
|
||||
onCancel = {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun sampleExpenseDtoWithConvertedAmountList(): List<ExpenseListItemUi> {
|
||||
val sampleCategories = listOf(
|
||||
Category(
|
||||
name = "Hotel",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.HOTEL,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Jedzenie",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.RESTAURANT,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Transport",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.FLIGHT,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Rozrywka",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.ATTRACTION,
|
||||
color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Zakupy",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.GROCERIES,
|
||||
color = colors.random()
|
||||
),
|
||||
)
|
||||
|
||||
val trip = Trip(
|
||||
id = 1,
|
||||
name = "Vacation",
|
||||
currency = "USD",
|
||||
startDate = LocalDate.parse("2026-01-01")
|
||||
)
|
||||
|
||||
val startLong = LocalDateTime.now().minusDays(10).toEpochMilli()
|
||||
val endLong = LocalDateTime.now().toEpochMilli()
|
||||
|
||||
val result: MutableList<ExpenseListItemUi> = mutableListOf()
|
||||
result.add(
|
||||
ExpenseListItemUi.Header(
|
||||
LocalDateTime.ofEpochSecond(
|
||||
Random.nextLong(startLong, endLong),
|
||||
0,
|
||||
ZoneOffset.UTC
|
||||
).toLocalDate(), Random.nextDouble(0.1, 300.0), Currencies.entries.random().name
|
||||
)
|
||||
)
|
||||
for (i in 0..15) {
|
||||
val category = sampleCategories.random()
|
||||
val datetime = if (i > 4) {
|
||||
LocalDateTime.ofEpochSecond(
|
||||
Random.nextLong(startLong, endLong),
|
||||
0,
|
||||
ZoneOffset.UTC
|
||||
)
|
||||
} else LocalDateTime.now()
|
||||
|
||||
val expense = Expense(
|
||||
id = i,
|
||||
categoryId = category.id,
|
||||
tripId = 1,
|
||||
amount = Random.nextDouble(0.1, 300.0),
|
||||
currency = Currencies.entries.random().name,
|
||||
note = if (i % 3 == 0) "Some note" else "",
|
||||
datetime = datetime,
|
||||
rate = if (Random.nextBoolean()) Random.nextDouble(
|
||||
0.1,
|
||||
5.0
|
||||
) else 1.0
|
||||
)
|
||||
|
||||
|
||||
val expenseDto = ExpenseDto(
|
||||
expense = expense,
|
||||
category = category,
|
||||
trip = trip
|
||||
)
|
||||
result.add(
|
||||
ExpenseListItemUi.Item(expenseDto)
|
||||
)
|
||||
if (i % 5 == 0) {
|
||||
result.add(
|
||||
ExpenseListItemUi.Header(
|
||||
datetime.toLocalDate(),
|
||||
Random.nextDouble(0.1, 300.0),
|
||||
Currencies.entries.random().name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package cc.n0th1ng.tripmoney.screens.managecategories
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SwipeToDismissBox
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.screens.AddCategoryDialog
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.categoriesToPreview
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import cc.n0th1ng.tripmoney.utils.colors
|
||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import com.composables.icons.materialsymbols.outlined.R
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.collections.emptyList
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun ManageCategoriesScreen() {
|
||||
val expenseAndCategoryViewModel: ExpenseAndCategoryViewModel = hiltViewModel()
|
||||
val categories by expenseAndCategoryViewModel.getCategories().collectAsState(emptyList())
|
||||
val archivedCategories by expenseAndCategoryViewModel.getArchivedCategories()
|
||||
.collectAsState(emptyList())
|
||||
ManageCategoriesScreen(
|
||||
categories = categories,
|
||||
archivedCategories = archivedCategories,
|
||||
onSaveCategory = { expenseAndCategoryViewModel.save(it) },
|
||||
onDeleteCategory = {
|
||||
expenseAndCategoryViewModel.delete(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun ManageCategoriesScreen(
|
||||
categories: List<Category>,
|
||||
archivedCategories: List<Category>,
|
||||
onSaveCategory: (Category) -> Unit,
|
||||
onDeleteCategory: (Category) -> Unit,
|
||||
) {
|
||||
|
||||
var categoryToEdit by remember { mutableStateOf<Category?>(null) }
|
||||
var showAddCategoryDialog by remember { mutableStateOf(false) }
|
||||
var itemToDelete by remember { mutableStateOf<Category?>(null) }
|
||||
var itemToArchive by remember { mutableStateOf<Category?>(null) }
|
||||
|
||||
Scaffold(floatingActionButtonPosition = FabPosition.EndOverlay, floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = { showAddCategoryDialog = true },
|
||||
icon = { Icon(Icons.Filled.Add, stringResource(string.add_new)) },
|
||||
text = { Text(text = stringResource(string.add_new)) },
|
||||
)
|
||||
})
|
||||
{
|
||||
LazyColumn(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
items(categories, key = { it.id }) { category ->
|
||||
SwipeToDeleteExpenseCard(
|
||||
category = category,
|
||||
onDelete = { itemToArchive = category },
|
||||
onClick = {
|
||||
categoryToEdit = category
|
||||
showAddCategoryDialog = true
|
||||
}
|
||||
)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
if (archivedCategories.isNotEmpty()) {
|
||||
item {
|
||||
CustomDivider()
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
}
|
||||
|
||||
items(archivedCategories, key = { it.id }) { archivedCategory ->
|
||||
SwipeToDeleteExpenseCard(
|
||||
category = archivedCategory,
|
||||
onDelete = { itemToDelete = archivedCategory },
|
||||
onClick = {
|
||||
categoryToEdit = archivedCategory
|
||||
showAddCategoryDialog = true
|
||||
},
|
||||
isArchived = true
|
||||
)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
}
|
||||
if (showAddCategoryDialog) {
|
||||
AddCategoryDialog(
|
||||
onDismiss = {
|
||||
showAddCategoryDialog = false
|
||||
}, onSave = { category ->
|
||||
onSaveCategory(category)
|
||||
showAddCategoryDialog = false
|
||||
},
|
||||
categoryToEdit = categoryToEdit
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (itemToDelete != null) {
|
||||
DeleteConfirmationDialog(
|
||||
bodyText = stringResource(string.delete_category_info),
|
||||
onConfirm = {
|
||||
onDeleteCategory(itemToDelete!!)
|
||||
itemToDelete = null
|
||||
},
|
||||
onCancel = {
|
||||
itemToDelete = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (itemToArchive != null) {
|
||||
DeleteConfirmationDialog(
|
||||
title = stringResource(string.you_want_archive),
|
||||
buttonText = stringResource(string.archive),
|
||||
bodyText = stringResource(string.archive_category_info),
|
||||
onConfirm = {
|
||||
onSaveCategory(itemToArchive!!.copy(archived = true))
|
||||
itemToArchive = null
|
||||
},
|
||||
onCancel = {
|
||||
itemToArchive = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
private fun CustomDivider() {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Absolute.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
"Archived",
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(Color.White.copy(alpha = 0f)),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun SwipeToDeleteExpenseCard(
|
||||
category: Category,
|
||||
onDelete: (Category) -> Unit,
|
||||
onClick: (Category) -> Unit,
|
||||
isArchived: Boolean = false
|
||||
) {
|
||||
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = { dismissValue ->
|
||||
if (dismissValue == SwipeToDismissBoxValue.EndToStart) {
|
||||
onDelete(category)
|
||||
false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
SwipeToDismissBox(
|
||||
state = dismissState,
|
||||
enableDismissFromStartToEnd = false,
|
||||
backgroundContent = {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(CardDefaults.elevatedShape)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.onError)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (isArchived) R.drawable.materialsymbols_ic_delete_outlined
|
||||
else R.drawable.materialsymbols_ic_archive_outlined
|
||||
),
|
||||
contentDescription = stringResource(string.delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
CategoryCard(
|
||||
category = category,
|
||||
onClick = onClick,
|
||||
isArchived = isArchived
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DeleteConfirmationDialog(
|
||||
title: String = stringResource(string.delete_confirmation),
|
||||
buttonText: String = stringResource(string.delete),
|
||||
bodyText: String = "",
|
||||
onConfirm: () -> Unit,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = { onCancel() }
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.background(
|
||||
MaterialTheme.colorScheme.secondaryContainer,
|
||||
shape = MaterialTheme.shapes.medium
|
||||
)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Text(
|
||||
text = bodyText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = onCancel,
|
||||
colors = ButtonDefaults.buttonColors().copy(containerColor = MaterialTheme.colorScheme.secondary),
|
||||
modifier = Modifier
|
||||
.padding(end = 10.dp)
|
||||
){
|
||||
Text(text = stringResource(string.cancel),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondary)
|
||||
}
|
||||
Button(
|
||||
onClick = onConfirm,
|
||||
colors = ButtonDefaults.buttonColors().copy(containerColor = MaterialTheme.colorScheme.error),
|
||||
){
|
||||
Text(text = buttonText,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CategoryCard(
|
||||
category: Category,
|
||||
onClick: (Category) -> Unit,
|
||||
isArchived: Boolean = false
|
||||
) {
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors()
|
||||
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.9f)
|
||||
.height(70.dp)
|
||||
.combinedClickable(
|
||||
enabled = true,
|
||||
onClick = { onClick(category) },
|
||||
onLongClick = { onClick(category) }),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 7.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.alpha(if (isArchived) 0.6f else 1f)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(15.dp),
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceDim,
|
||||
shape = MaterialTheme.shapes.small
|
||||
)
|
||||
.padding(10.dp),
|
||||
painter = painterResource(category.icon.resource),
|
||||
contentDescription = "Category",
|
||||
tint = Color(category.color.toColorInt())
|
||||
)
|
||||
|
||||
Column()
|
||||
{
|
||||
Text(
|
||||
text = category.name,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewManageCategoriesScreen() {
|
||||
TripMoneyTheme {
|
||||
ManageCategoriesScreen(categories = categoriesToPreview.subList(0,2), categoriesToPreview.subList(3,5), {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewAddCategoryDialog() {
|
||||
TripMoneyTheme {
|
||||
AddCategoryDialog(
|
||||
onDismiss = {},
|
||||
onSave = {})
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewEditCategoryDialog() {
|
||||
TripMoneyTheme {
|
||||
AddCategoryDialog(
|
||||
onDismiss = {},
|
||||
onSave = {},
|
||||
categoryToEdit = Category(
|
||||
0, "Hotel",
|
||||
icon = cc.n0th1ng.tripmoney.utils.Icons.entries.random(),
|
||||
color = colors.random(),
|
||||
archived = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewDeleteConfirmationDialog() {
|
||||
TripMoneyTheme {
|
||||
DeleteConfirmationDialog(
|
||||
onConfirm = {},
|
||||
onCancel = {},
|
||||
bodyText = "Your all expenses with category Hotel will be removed.",
|
||||
title = "Do you want to delete?",
|
||||
buttonText = "Delete"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,16 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import cc.n0th1ng.tripmoney.R.*
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.data.repository.AppTheme
|
||||
import cc.n0th1ng.tripmoney.navigation.Screens
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.CategorySelectionDialog
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.CurrencySelectionDialog
|
||||
import cc.n0th1ng.tripmoney.screens.statistics.categories
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
@@ -60,7 +66,7 @@ import java.nio.file.Files
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
@Composable
|
||||
fun SettingsScreen() {
|
||||
fun SettingsScreen(navController: NavHostController) {
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||
val currentTheme by settingsViewModel.theme.collectAsState()
|
||||
val currentDefaultCurrency by settingsViewModel.defaultCurrency.collectAsState()
|
||||
@@ -68,6 +74,7 @@ fun SettingsScreen() {
|
||||
val expenseAndCategoryViewModel: ExpenseAndCategoryViewModel = hiltViewModel()
|
||||
val tripViewModel: TripViewModel = hiltViewModel()
|
||||
val currentTrip by tripViewModel.getTrip(currentTripId).collectAsState(Trip.DUMMY)
|
||||
val categories by expenseAndCategoryViewModel.getCategories().collectAsState(emptyList())
|
||||
val context = LocalContext.current
|
||||
val tripName = currentTrip?.name ?: ""
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -90,7 +97,8 @@ fun SettingsScreen() {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onCategoriesClick = {navController.navigate(Screens.MANAGE_CATEGORIES)}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -103,11 +111,13 @@ fun SettingsScreen(
|
||||
onCurrencySave: (Currencies) -> Unit,
|
||||
tripName: String,
|
||||
onExportToCsv: () -> Unit,
|
||||
onCategoriesClick: () -> Unit
|
||||
) {
|
||||
|
||||
Scaffold { padding ->
|
||||
var showThemeDialog by remember { mutableStateOf(false) }
|
||||
var showCurrencyDialog by remember { mutableStateOf(false) }
|
||||
var showCategoriesDialog by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -142,9 +152,15 @@ fun SettingsScreen(
|
||||
SettingsListItem(
|
||||
onClick = onExportToCsv,
|
||||
stringResource(string.export_to_csv),
|
||||
supportingText = "Save expenses from %s to a file".format(tripName),
|
||||
supportingText = stringResource(string.export_csv_subttext).format(tripName),
|
||||
iconResource = R.drawable.materialsymbols_ic_csv_outlined
|
||||
)
|
||||
SettingsListItem(
|
||||
onClick = onCategoriesClick,
|
||||
stringResource(string.categories),
|
||||
supportingText = stringResource(string.manage_categories),
|
||||
iconResource = R.drawable.materialsymbols_ic_label_outlined
|
||||
)
|
||||
|
||||
if (showThemeDialog) {
|
||||
ThemeSelectionDialog(
|
||||
@@ -258,7 +274,14 @@ fun ThemeSelectionDialog(
|
||||
@Composable
|
||||
fun PreviewSettingsScreen() {
|
||||
TripMoneyTheme {
|
||||
SettingsScreen(Currencies.entries.random(), AppTheme.entries.random(), {}, {}, "Włochy", {})
|
||||
SettingsScreen(
|
||||
Currencies.entries.random(),
|
||||
AppTheme.entries.random(),
|
||||
{},
|
||||
{},
|
||||
"Włochy",
|
||||
{},
|
||||
{})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cc.n0th1ng.tripmoney.screens.statistics
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
@@ -13,9 +14,15 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -30,6 +37,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
@@ -82,9 +90,10 @@ fun StatisticsScreen(
|
||||
|
||||
@Composable
|
||||
fun Summary(summaryAmount: Double, currency: String) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.elevatedCardColors()
|
||||
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -94,7 +103,10 @@ fun Summary(summaryAmount: Double, currency: String) {
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column {
|
||||
Text(stringResource(cc.n0th1ng.tripmoney.R.string.total_expenses), style = MaterialTheme.typography.titleSmall)
|
||||
Text(
|
||||
stringResource(cc.n0th1ng.tripmoney.R.string.total_expenses),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
Text(
|
||||
"%.2f %s".format(summaryAmount, currency),
|
||||
style = MaterialTheme.typography.headlineLarge
|
||||
@@ -118,7 +130,11 @@ fun Summary(summaryAmount: Double, currency: String) {
|
||||
|
||||
@Composable
|
||||
fun SummaryPerCategoryCard(summaryPerCategoryList: List<SummaryPerCategory>) {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.elevatedCardColors()
|
||||
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(15.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp)
|
||||
@@ -191,16 +207,19 @@ fun CategoryCard(modifier: Modifier = Modifier, summaryPerCategory: SummaryPerCa
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun Preview() {
|
||||
TripMoneyTheme {
|
||||
StatisticsScreen(
|
||||
summaryPerCategoryList,
|
||||
summaryAmount = 125.24,
|
||||
Currencies.entries.random()
|
||||
)
|
||||
Scaffold {
|
||||
StatisticsScreen(
|
||||
summaryPerCategoryList,
|
||||
summaryAmount = 125.24,
|
||||
Currencies.entries.random()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -21,7 +20,6 @@ import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -50,16 +48,21 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.navigation.Screens
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.AddExpenseBottomSheet
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.DeleteConfirmationDialog
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import java.time.LocalDate
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@@ -70,9 +73,38 @@ fun TripPickerScreen(
|
||||
) {
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||
val tripViewModel: TripViewModel = hiltViewModel()
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
val trips: LazyPagingItems<Trip> = tripViewModel.getTrips().collectAsLazyPagingItems()
|
||||
val tripsFlow = tripViewModel.getTrips()
|
||||
val currentTripId by settingsViewModel.currentTrip.collectAsState()
|
||||
|
||||
TripPickerScreen(
|
||||
tripsFlow = tripsFlow,
|
||||
currentTripId = currentTripId,
|
||||
onDelete = { trip -> tripViewModel.delete(trip) },
|
||||
onClick = { trip ->
|
||||
settingsViewModel.setCurrentTrip(trip.id)
|
||||
navController.navigate(Screens.LIST_EXPENSE)
|
||||
},
|
||||
onSave = { trip ->
|
||||
tripViewModel.save(trip)
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
fun TripPickerScreen(
|
||||
tripsFlow: Flow<PagingData<Trip>>,
|
||||
currentTripId: Int,
|
||||
onDelete: (Trip) -> Unit,
|
||||
onClick: (Trip) -> Unit,
|
||||
onSave: (Trip) -> Unit
|
||||
) {
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
val trips: LazyPagingItems<Trip> = tripsFlow.collectAsLazyPagingItems()
|
||||
|
||||
var tripToEdit by remember { mutableStateOf<Trip?>(null) }
|
||||
Scaffold(floatingActionButtonPosition = FabPosition.EndOverlay, floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
@@ -91,12 +123,12 @@ fun TripPickerScreen(
|
||||
val trip = trips[i]
|
||||
if (trip != null) {
|
||||
SwipeToDeleteTripCard(
|
||||
trip, onDelete = {
|
||||
tripViewModel.delete(trip)
|
||||
}, onClick = {
|
||||
settingsViewModel.setCurrentTrip(trip.id)
|
||||
navController.navigate(Screens.LIST_EXPENSE)
|
||||
}, isSelected = currentTripId == trip.id,
|
||||
trip = trip,
|
||||
onDelete = {
|
||||
onDelete(trip)
|
||||
}, onClick = {
|
||||
onClick(trip)
|
||||
}, isSelected = currentTripId == trip.id,
|
||||
onLongClick = { trip ->
|
||||
tripToEdit = trip
|
||||
showBottomSheet = true
|
||||
@@ -113,7 +145,7 @@ fun TripPickerScreen(
|
||||
tripToEdit = null
|
||||
},
|
||||
onSave = { trip ->
|
||||
tripViewModel.save(trip)
|
||||
onSave(trip)
|
||||
showBottomSheet = false
|
||||
tripToEdit = null
|
||||
},
|
||||
@@ -152,7 +184,6 @@ fun SwipeToDeleteTripCard(
|
||||
}
|
||||
|
||||
SwipeToDismissBox(
|
||||
modifier = Modifier.alpha(if (isSelected) 1.0f else 0.7f),
|
||||
state = dismissState,
|
||||
enableDismissFromStartToEnd = false,
|
||||
backgroundContent = {
|
||||
@@ -160,7 +191,7 @@ fun SwipeToDeleteTripCard(
|
||||
Modifier
|
||||
.clip(CardDefaults.elevatedShape)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.onError)
|
||||
.background(MaterialTheme.colorScheme.errorContainer)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
@@ -186,7 +217,7 @@ fun TripCard(
|
||||
containerColor = if (isSelected) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
),
|
||||
modifier = Modifier
|
||||
@@ -195,7 +226,7 @@ fun TripCard(
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onLongClick(trip)
|
||||
}, onClick = { onClick(trip) }),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = if (isSelected) 7.dp else 0.dp)
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 7.dp)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -216,4 +247,39 @@ fun TripCard(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewTripPickerScreen() {
|
||||
val tripsToPreview = listOf(
|
||||
Trip(
|
||||
1,
|
||||
name = "Włochy",
|
||||
startDate = LocalDate.parse("2026-03-01"),
|
||||
currency = "PLN"
|
||||
),
|
||||
Trip(
|
||||
2,
|
||||
name = "Szwajcaria",
|
||||
startDate = LocalDate.parse("2025-03-01"),
|
||||
currency = "EUR"
|
||||
),
|
||||
Trip(
|
||||
3,
|
||||
name = "Portugalia",
|
||||
startDate = LocalDate.parse("2025-03-01"),
|
||||
currency = "USD"
|
||||
)
|
||||
)
|
||||
TripMoneyTheme {
|
||||
TripPickerScreen(
|
||||
tripsFlow = MutableStateFlow(PagingData.from(tripsToPreview)),
|
||||
currentTripId = 1,
|
||||
onDelete = {},
|
||||
onClick = {},
|
||||
onSave = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user