This commit is contained in:
Rafal Wisniewski
2026-04-02 10:46:41 +02:00
parent c4c9868698
commit 767d54e8f6
20 changed files with 381 additions and 135 deletions

View File

@@ -2,13 +2,10 @@ package cc.n0th1ng.tripmoney.screens.addexpense
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
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -34,7 +31,6 @@ import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
@@ -47,7 +43,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -76,8 +71,6 @@ import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
import com.composables.icons.materialsymbols.outlined.R.drawable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.time.LocalDate
import java.time.LocalDateTime
@@ -104,7 +97,7 @@ fun AddExpenseBottomSheet(
onDismiss = onDismiss,
expenseDtoToEdit = expenseDtoToEdit,
state = state,
currentTrip = currentTrip!!,
currentTrip = currentTrip ?: Trip.DUMMY,
categories = categories
)
}
@@ -128,10 +121,14 @@ fun AddExpenseBottomSheet(
var amount by remember {
mutableStateOf(
expenseDtoToEdit?.expense?.amount?.toString() ?: "0.00"
"%.2f".format(expenseDtoToEdit?.expense?.amount ?: 0.00)
)
}
var equationResult by remember {
mutableDoubleStateOf(
expenseDtoToEdit?.expense?.amount ?: 0.00
)
}
var equationResult by remember { mutableDoubleStateOf(0.0) }
val dummyFocusRequester = remember { FocusRequester() }
var showCurrencyDialog by remember { mutableStateOf(false) }
var showCategoryDialog by remember { mutableStateOf(false) }
@@ -193,7 +190,7 @@ fun AddExpenseBottomSheet(
fontWeight = FontWeight.Bold
)
Text(
text = if (amount.contains(Regex("[+\\/*-]\\d+"))) "%.2f".format(
text = if (amount.contains(Regex("[+/*-]\\d+"))) "%.2f".format(
equationResult
) else "",
fontSize = 14.sp,
@@ -240,7 +237,7 @@ fun AddExpenseBottomSheet(
NumberKeyboard(
modifier = Modifier.fillMaxWidth(),
onOperatorClick = { operator ->
if (amount.isDoubleTwoDigitsOrEquation() && amount.contains(Regex("[+\\/*-]\\d+"))) {
if (amount.isDoubleTwoDigitsOrEquation() && amount.contains(Regex("[+/*-]\\d+"))) {
amount = evaluate(amount).toString()
}
val newText = amount + operator
@@ -369,7 +366,7 @@ private inline fun String.indexOfFirstIndexed(predicate: (index: Int, Char) -> B
}
private fun String.isDoubleTwoDigitsOrEquation(): Boolean {
return this != "0.00" && this.matches(Regex("^(-?(0\\.?|0\\.\\d{1,2}|[1-9]\\d*(\\.\\d{0,2})?))([+\\/*-](0\\.?|0\\.\\d{1,2}|[1-9]\\d*(\\.\\d{0,2})?)?)?$"))
return this != "0.00" && this.matches(Regex("^(-?(0\\.?|0\\.\\d{1,2}|[1-9]\\d*(\\.\\d{0,2})?))([+/*-](0\\.?|0\\.\\d{1,2}|[1-9]\\d*(\\.\\d{0,2})?)?)?$"))
}
@Composable
@@ -518,7 +515,6 @@ fun KeyboardButton(
text: String? = null,
icon: Painter? = null,
onClick: () -> Unit,
enabled: Boolean = true,
onLongClick: () -> Unit = {},
containerColor: Color = MaterialTheme.colorScheme.primary,
contentColor: Color = MaterialTheme.colorScheme.onPrimary
@@ -574,6 +570,7 @@ fun PreviewAddExpenseDisabled() {
1,
"Trip",
LocalDate.parse("2020-01-01"),
LocalDate.parse("2020-01-15"),
Currencies.entries.random().name
),
categories = categoriesToPreview
@@ -607,13 +604,17 @@ fun PreviewAddExpenseEnabled() {
tripId = 1
),
category = categoriesToPreview[0],
Trip(1, "Włochy", LocalDate.parse("2025-01-02"), "PLN")
Trip(
1, "Włochy", LocalDate.parse("2025-01-02"),
LocalDate.parse("2025-01-15"), "PLN"
)
),
state = sheetState,
currentTrip = Trip(
1,
"Trip",
LocalDate.parse("2020-01-01"),
LocalDate.parse("2020-01-11"),
Currencies.entries.random().name
),
categories = categoriesToPreview

View File

@@ -5,14 +5,14 @@ import androidx.annotation.RequiresApi
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.DatePickerState
import androidx.compose.material3.DateRangePicker
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TimePicker
import androidx.compose.material3.TimePickerState
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberDateRangePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -20,17 +20,55 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import cc.n0th1ng.tripmoney.R.*
import java.sql.Time
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Calendar
@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DateRangePicker(
startDate: LocalDate,
endDate: LocalDate,
onDismiss: () -> Unit,
onConfirm: (LocalDate, LocalDate) -> Unit
) {
val datePickerState =
rememberDateRangePickerState(initialSelectedStartDateMillis = startDate.toEpochMilli(),
initialSelectedEndDateMillis = endDate.toEpochMilli())
DatePickerDialog(
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = {
val selectedStartDateMillis = datePickerState.selectedStartDateMillis
val selectedEndDateMillis = datePickerState.selectedEndDateMillis
if (selectedStartDateMillis != null && selectedEndDateMillis != null) {
val selectedStartDate = Instant.ofEpochMilli(selectedStartDateMillis)
.atZone(ZoneId.systemDefault())
.toLocalDate()
val selectedEndDate =
Instant.ofEpochMilli(selectedEndDateMillis).atZone(ZoneId.systemDefault())
.toLocalDate()
onConfirm(selectedStartDate, selectedEndDate)
}
}) {
Text("OK")
}
},
dismissButton = {
TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) }
}
) {
DateRangePicker(state = datePickerState, showModeToggle = false,
title = {})
}
}
@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

@@ -46,14 +46,12 @@ 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
import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
@@ -73,7 +71,6 @@ 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
@@ -83,7 +80,9 @@ import kotlin.random.Random
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun ListExpenseScreen(filter: String) {
fun ListExpenseScreen(filter: String,
initialAutoOpen: Boolean,
onAutoOpenConsumed: () -> Unit ) {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val tripViewModel: TripViewModel = hiltViewModel()
val currentTripId by settingsViewModel.currentTrip.collectAsState()
@@ -97,7 +96,9 @@ fun ListExpenseScreen(filter: String) {
expensesFlow = expensesFlow,
onSaveExpense = { expenseAndCategoryViewModel.save(it, currentTrip!!) },
onDeleteExpense = { expenseAndCategoryViewModel.delete(it) },
isRecalculatingRate = isRecalculatingRate
isRecalculatingRate = isRecalculatingRate,
initialAutoOpen = initialAutoOpen,
onAutoOpenConsumed = onAutoOpenConsumed
)
}
@@ -108,12 +109,23 @@ fun ListExpenseScreen(filter: String) {
fun ListExpenseScreen(
expensesFlow: Flow<PagingData<ExpenseListItemUi>>,
onSaveExpense: (Expense) -> Unit, onDeleteExpense: (Expense) -> Unit,
isRecalculatingRate: Boolean
isRecalculatingRate: Boolean,
initialAutoOpen: Boolean,
onAutoOpenConsumed: () -> Unit
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var showBottomSheet by remember { mutableStateOf(false) }
LaunchedEffect(initialAutoOpen) {
if (initialAutoOpen) {
showBottomSheet = true
onAutoOpenConsumed()
}
}
val items = expensesFlow.collectAsLazyPagingItems()
val listState = rememberLazyListState()
var showBottomSheet by remember { mutableStateOf(false) }
var expenseDtoToEdit by remember { mutableStateOf<ExpenseDto?>(null) }
var itemToDelete by remember { mutableStateOf<Expense?>(null) }
@@ -196,7 +208,7 @@ fun ListExpenseScreen(
showBottomSheet = false
},
expenseDtoToEdit = expenseDtoToEdit,
state = rememberModalBottomSheetState(skipPartiallyExpanded = true)
state = sheetState
)
}
}
@@ -216,7 +228,9 @@ fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
date.format(
DateTimeFormatter.ofPattern("dd EEEE")
).toString(),
modifier = Modifier.padding(horizontal = 5.dp).background(Color.White.copy(alpha = 0f)),
modifier = Modifier
.padding(horizontal = 5.dp)
.background(Color.White.copy(alpha = 0f)),
style = MaterialTheme.typography.titleMedium
)
Row(
@@ -446,7 +460,9 @@ fun PreviewListExpenseScreen() {
expensesFlow = MutableStateFlow(pagingData),
onSaveExpense = {},
onDeleteExpense = {},
true
isRecalculatingRate = true,
false,
{}
)
}
@@ -497,7 +513,8 @@ private fun sampleExpenseDtoWithConvertedAmountList(): List<ExpenseListItemUi> {
id = 1,
name = "Vacation",
currency = "USD",
startDate = LocalDate.parse("2026-01-01")
startDate = LocalDate.parse("2026-01-01"),
endDate = LocalDate.parse("2026-01-11"),
)
val startLong = LocalDateTime.now().minusDays(10).toEpochMilli()

View File

@@ -19,6 +19,7 @@ import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -69,12 +70,12 @@ import java.nio.file.Files
fun SettingsScreen(navController: NavHostController) {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val currentTheme by settingsViewModel.theme.collectAsState()
val currentAddExpenseSwitch by settingsViewModel.addExpenseSwitch.collectAsState()
val currentDefaultCurrency by settingsViewModel.defaultCurrency.collectAsState()
val currentTripId by settingsViewModel.currentTrip.collectAsState()
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()
@@ -84,6 +85,9 @@ fun SettingsScreen(navController: NavHostController) {
currentTheme = currentTheme,
onThemeSave = { settingsViewModel.setTheme(it) },
onCurrencySave = { settingsViewModel.setDefaultCurrency(it) },
onAddExpenseSwitch = {
settingsViewModel.setCurrentAddExpenseSwitch(it)
},
tripName = tripName,
onExportToCsv = {
scope.launch {
@@ -98,7 +102,8 @@ fun SettingsScreen(navController: NavHostController) {
}
}
},
onCategoriesClick = {navController.navigate(Screens.MANAGE_CATEGORIES)}
onCategoriesClick = { navController.navigate(Screens.MANAGE_CATEGORIES) },
currentAddExpenseSwitch = currentAddExpenseSwitch
)
}
@@ -111,13 +116,14 @@ fun SettingsScreen(
onCurrencySave: (Currencies) -> Unit,
tripName: String,
onExportToCsv: () -> Unit,
onCategoriesClick: () -> Unit
onCategoriesClick: () -> Unit,
onAddExpenseSwitch: (Boolean) -> Unit,
currentAddExpenseSwitch: Boolean
) {
Scaffold { padding ->
var showThemeDialog by remember { mutableStateOf(false) }
var showCurrencyDialog by remember { mutableStateOf(false) }
var showCategoriesDialog by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxWidth()
@@ -161,6 +167,15 @@ fun SettingsScreen(
supportingText = stringResource(string.manage_categories),
iconResource = R.drawable.materialsymbols_ic_label_outlined
)
SettingsListItem(
onClick = onCategoriesClick,
stringResource(string.add_expense),
supportingText = stringResource(string.add_expense_settings),
iconResource = R.drawable.materialsymbols_ic_payments_outlined,
trailingContent = {
Switch(checked = currentAddExpenseSwitch, onCheckedChange = {onAddExpenseSwitch(it)})
}
)
if (showThemeDialog) {
ThemeSelectionDialog(
@@ -209,7 +224,7 @@ fun SettingsListItem(
headlineText: String,
trailingContent: @Composable () -> Unit = {},
supportingText: String,
iconResource: Int
iconResource: Int,
) {
Card {
ListItem(
@@ -226,6 +241,7 @@ fun SettingsListItem(
}
}
@Composable
fun ThemeSelectionDialog(
onDismiss: () -> Unit,
@@ -275,13 +291,16 @@ fun ThemeSelectionDialog(
fun PreviewSettingsScreen() {
TripMoneyTheme {
SettingsScreen(
Currencies.entries.random(),
AppTheme.entries.random(),
{},
{},
"Włochy",
{},
{})
currentDefaultCurrency = Currencies.entries.random(),
currentTheme = AppTheme.entries.random(),
onThemeSave = {},
onCurrencySave = {},
onExportToCsv = {},
tripName = "Włochy",
onCategoriesClick = {},
onAddExpenseSwitch = {},
currentAddExpenseSwitch = false
)
}
}

View File

@@ -14,12 +14,8 @@ 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
@@ -31,13 +27,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
import androidx.compose.ui.res.colorResource
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.core.graphics.toColorLong
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
@@ -66,7 +62,8 @@ fun StatisticsScreen() {
StatisticsScreen(
summaryPerCategoryList,
summaryAmount,
Currencies.valueOf(currentTrip?.currency ?: Currencies.default().name)
Currencies.valueOf(currentTrip?.currency ?: Currencies.default().name),
expenseAndCategoryViewModel.getBudgetLeft(currentTripId)
)
}
@@ -75,7 +72,8 @@ fun StatisticsScreen() {
fun StatisticsScreen(
summaryPerCategoryList: List<SummaryPerCategory>,
summaryAmount: Double,
tripCurrency: Currencies
tripCurrency: Currencies,
moneyLeft: Double
) {
Column(
modifier = Modifier
@@ -83,48 +81,70 @@ fun StatisticsScreen(
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Summary(summaryAmount, tripCurrency.name)
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Summary(
Modifier.weight(1f), -1 * summaryAmount, tripCurrency.name,
stringResource(cc.n0th1ng.tripmoney.R.string.total_expenses),
R.drawable.materialsymbols_ic_payment_arrow_down_outlined,
iconColor = MaterialTheme.colorScheme.error
)
Summary(
Modifier.weight(1f), moneyLeft, tripCurrency.name,
stringResource(cc.n0th1ng.tripmoney.R.string.money_left),
R.drawable.materialsymbols_ic_payments_outlined,
iconColor = colorResource(cc.n0th1ng.tripmoney.R.color.good_green)
)
}
SummaryPerCategoryCard(summaryPerCategoryList)
}
}
@Composable
fun Summary(summaryAmount: Double, currency: String) {
fun Summary(
modifier: Modifier = Modifier,
amount: Double,
currency: String,
text: String,
icon: Int,
iconColor: Color
) {
ElevatedCard(
modifier = Modifier.fillMaxWidth(),
modifier = modifier,
colors = CardDefaults.elevatedCardColors()
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Column {
Row(
modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
Icon(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surfaceDim,
shape = MaterialTheme.shapes.small
)
.padding(5.dp),
painter = painterResource(icon),
tint = iconColor,
contentDescription = null,
)
Text(
stringResource(cc.n0th1ng.tripmoney.R.string.total_expenses),
text,
style = MaterialTheme.typography.titleSmall
)
Text(
"%.2f %s".format(summaryAmount, currency),
style = MaterialTheme.typography.headlineLarge
)
}
Row(
horizontalArrangement = Arrangement.Center
Text(
"%.2f %s".format(amount, currency),
style = MaterialTheme.typography.titleLarge,
)
{
Icon(
painter = painterResource(R.drawable.materialsymbols_ic_payment_arrow_down_outlined),
contentDescription = null,
modifier = Modifier.size(45.dp)
)
}
}
}
}
@@ -217,7 +237,8 @@ fun Preview() {
StatisticsScreen(
summaryPerCategoryList,
summaryAmount = 125.24,
Currencies.entries.random()
Currencies.entries.random(),
432.14
)
}
}

View File

@@ -29,6 +29,7 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -37,6 +38,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -47,9 +49,11 @@ import cc.n0th1ng.tripmoney.data.entity.Trip
import cc.n0th1ng.tripmoney.screens.addexpense.CurrencyButton
import cc.n0th1ng.tripmoney.screens.listexpense.CurrencySelectionDialog
import cc.n0th1ng.tripmoney.screens.listexpense.DatePicker
import cc.n0th1ng.tripmoney.screens.listexpense.DateRangePicker
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
import cc.n0th1ng.tripmoney.utils.AllPreviews
import cc.n0th1ng.tripmoney.utils.Currencies
import cc.n0th1ng.tripmoney.utils.pretty
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
import io.ktor.http.hostIsIp
import kotlinx.coroutines.CoroutineScope
@@ -98,8 +102,15 @@ fun AddTripBottomSheet(
)
}
var endDate by remember {
mutableStateOf(
tripToEdit?.startDate ?: LocalDate.now()
)
}
var showCurrencyDialog by remember { mutableStateOf(false) }
var showDatePicker by remember { mutableStateOf(false) }
var budgetString by remember { mutableStateOf(tripToEdit?.budget?.toString() ?: "") }
var currency by remember { mutableStateOf(tripToEdit?.currency ?: defaultCurrency.name) }
var enableSave by remember { mutableStateOf(tripToEdit != null) }
@@ -127,6 +138,23 @@ fun AddTripBottomSheet(
name = newText
enableSave = !name.isEmpty()
})
Row(
modifier = Modifier.fillMaxWidth(0.9f),
horizontalArrangement = Arrangement.spacedBy(15.dp),
verticalAlignment = Alignment.CenterVertically
) {
BudgetInput(
modifier = Modifier.fillMaxWidth(0.7f),
budget = budgetString,
onTextChange = { newBudget -> budgetString = newBudget })
CurrencyButton(
modifier = Modifier
.weight(1f)
.fillMaxWidth(1f),
onClick = { showCurrencyDialog = true }, text = currency
)
}
Row(
modifier = Modifier.fillMaxWidth(0.9f),
horizontalArrangement = Arrangement.spacedBy(10.dp)
@@ -137,17 +165,14 @@ fun AddTripBottomSheet(
.weight(1f),
shape = MaterialTheme.shapes.medium,
onClick = { showDatePicker = true }) {
val startDateFormatted = startDate.pretty()
val endDateFormatted = endDate.pretty()
Text(
text = startDate.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")),
text = "$startDateFormatted - $endDateFormatted",
fontSize = 17.sp
)
}
CurrencyButton(
modifier = Modifier
.weight(1f)
.fillMaxWidth(1f),
onClick = { showCurrencyDialog = true }, text = currency
)
}
@@ -157,7 +182,13 @@ fun AddTripBottomSheet(
shape = MaterialTheme.shapes.medium,
onClick = {
val trip =
Trip(name = name, startDate = startDate, currency = currency)
Trip(
name = name,
startDate = startDate,
endDate = endDate,
currency = currency,
budget = budgetString.toDoubleOrNull() ?: 0.0
)
onSave(if (tripToEdit == null) trip else trip.copy(id = tripToEdit.id))
}) {
@@ -182,10 +213,15 @@ fun AddTripBottomSheet(
}
if (showDatePicker) {
DatePicker(startDate, onDismiss = { showDatePicker = false }, onConfirm = { newDate ->
startDate = newDate
showDatePicker = false
})
DateRangePicker(
startDate = startDate,
endDate = endDate,
onDismiss = { showDatePicker = false },
onConfirm = { newStartDate, newEndDate ->
startDate = newStartDate
endDate = newEndDate
showDatePicker = false
})
}
}
@@ -201,6 +237,28 @@ fun NameInput(name: String, onTextChange: (String) -> Unit) {
)
}
@Composable
fun BudgetInput(modifier: Modifier = Modifier, budget: String, onTextChange: (String) -> Unit) {
var text by remember { mutableStateOf(budget) }
OutlinedTextField(
placeholder = { Text("0.0") },
modifier = modifier,
label = { Text(stringResource(R.string.budget)) },
value = text,
onValueChange = { newText ->
val regex = Regex("^\\d*\\.?\\d{0,2}$")
if (regex.matches(newText)) {
text = newText
onTextChange(text)
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Decimal,
imeAction = ImeAction.Done
)
)
}
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("CoroutineCreationDuringComposition")
@@ -231,7 +289,8 @@ fun PreviewAddTripBottomSheetEditTrip() {
AddTripBottomSheet(
{},
{},
Trip(1, "Włochy", LocalDate.parse("2025-01-02"), "PLN"),
Trip(1, "Włochy", LocalDate.parse("2025-01-02"),
LocalDate.parse("2025-01-15"), "PLN", budget = 0.0),
sheetState,
defaultCurrency = Currencies.entries.random()
)

View File

@@ -58,6 +58,7 @@ import cc.n0th1ng.tripmoney.navigation.Screens
import cc.n0th1ng.tripmoney.screens.listexpense.DeleteConfirmationDialog
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
import cc.n0th1ng.tripmoney.utils.AllPreviews
import cc.n0th1ng.tripmoney.utils.pretty
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
import kotlinx.coroutines.flow.Flow
@@ -204,6 +205,7 @@ fun SwipeToDeleteTripCard(
}
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun TripCard(
trip: Trip,
@@ -236,15 +238,34 @@ fun TripCard(
Column(
modifier = Modifier.padding(16.dp)
) {
Text(fontSize = 25.sp, fontWeight = FontWeight.SemiBold, text = trip.name)
Text(trip.startDate.toString())
Text(
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.SemiBold,
text = trip.name
)
Text(
style = MaterialTheme.typography.bodySmall,
text = "start: " + trip.startDate.pretty() + "\nend: " + trip.endDate.pretty()
)
}
Column(
modifier = Modifier.padding(end = 20.dp),
horizontalAlignment = Alignment.End) {
Text(
trip.currency.uppercase(),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.SemiBold
)
Text(
"budget:",
style = MaterialTheme.typography.bodySmall,
)
Text(
"%.2f".format(trip.budget),
style = MaterialTheme.typography.bodySmall,
)
}
Text(
trip.currency.uppercase(),
modifier = Modifier.padding(20.dp),
fontSize = 20.sp,
fontWeight = FontWeight.SemiBold
)
}
}
}
@@ -258,18 +279,22 @@ fun PreviewTripPickerScreen() {
1,
name = "Włochy",
startDate = LocalDate.parse("2026-03-01"),
currency = "PLN"
endDate = LocalDate.parse("2026-03-14"),
currency = "PLN",
budget = 1053.53
),
Trip(
2,
name = "Szwajcaria",
startDate = LocalDate.parse("2025-03-01"),
endDate = LocalDate.parse("2025-03-11"),
currency = "EUR"
),
Trip(
3,
name = "Portugalia",
startDate = LocalDate.parse("2025-03-01"),
endDate = LocalDate.parse("2025-03-11"),
currency = "USD"
)
)