This commit is contained in:
Rafal Wisniewski
2026-03-19 21:02:10 +01:00
parent b074c98f7d
commit f625a6975c
13 changed files with 408 additions and 123 deletions

View File

@@ -2,6 +2,7 @@ package cc.n0th1ng.tripmoney.data.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
@@ -19,4 +20,6 @@ interface TripDao {
)
fun tripsPaged(): PagingSource<Int, Trip>
@Delete
suspend fun delete(trip: Trip)
}

View File

@@ -22,4 +22,9 @@ class TripRepository @Inject constructor(private val tripDao: TripDao) {
pagingSourceFactory = { tripDao.tripsPaged() }
).flow
}
@WorkerThread
suspend fun delete(trip: Trip) {
tripDao.delete(trip)
}
}

View File

@@ -38,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.graphics.toColorInt
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import cc.n0th1ng.tripmoney.R
import cc.n0th1ng.tripmoney.data.entity.Category
import cc.n0th1ng.tripmoney.data.entity.Expense
@@ -46,9 +47,11 @@ import cc.n0th1ng.tripmoney.screens.listexpense.CategorySelectionDialog
import cc.n0th1ng.tripmoney.screens.listexpense.CurrencySelectionDialog
import cc.n0th1ng.tripmoney.screens.listexpense.DateTimePicker
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
import cc.n0th1ng.tripmoney.utils.Currencies
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@OptIn(ExperimentalMaterial3Api::class)
@@ -57,11 +60,10 @@ import java.time.LocalDateTime
fun AddExpenseBottomSheet(
onSave: (Expense) -> Unit,
onDismiss: () -> Unit,
settingsViewModel: SettingsViewModel,
categories: List<Category>,
expenseAndCategoryViewModel: ExpenseAndCategoryViewModel,
expenseDtoToEdit: ExpenseDto?
) {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val currentTripId by settingsViewModel.currentTrip.collectAsState()
var amount by remember {
mutableStateOf(
@@ -70,11 +72,18 @@ fun AddExpenseBottomSheet(
}
var showCurrencyDialog by remember { mutableStateOf(false) }
var showCategoryDialog by remember { mutableStateOf(false) }
var currency by remember { mutableStateOf(expenseDtoToEdit?.expense?.currency ?: "PLN") }
var showDateTimePicker by remember { mutableStateOf(false) }
var currency by remember {
mutableStateOf(
expenseDtoToEdit?.expense?.currency ?: Currencies.PLN.name
)
}
var category by remember { mutableStateOf(expenseDtoToEdit?.category ?: categories[0]) }
var datetime by remember {
mutableStateOf(
LocalDateTime.parse(expenseDtoToEdit?.expense?.datetime ?: LocalDateTime.now().toString())
LocalDateTime.parse(
expenseDtoToEdit?.expense?.datetime ?: LocalDateTime.now().toString()
)
)
}
var note by remember { mutableStateOf(expenseDtoToEdit?.expense?.note ?: "") }
@@ -103,10 +112,12 @@ fun AddExpenseBottomSheet(
CurrencyButton(onClick = { showCurrencyDialog = true }, text = currency)
}
Spacer(Modifier.height(14.dp))
DateTimePicker(
dateTime = datetime,
onChange = { datetime = it }
)
OutlinedButton(onClick = { showDateTimePicker = true }) {
Text(
text = datetime.format(DateTimeFormatter.ofPattern("dd.MM HH:mm")),
fontSize = 17.sp
)
}
Spacer(Modifier.height(14.dp))
CategoryButton(onClick = { showCategoryDialog = true }, category = category)
Spacer(Modifier.height(14.dp))
@@ -155,7 +166,12 @@ fun AddExpenseBottomSheet(
}
}
if (showDateTimePicker) {
DateTimePicker(datetime, onChange = { newDateTime ->
datetime = newDateTime
showDateTimePicker = false
})
}
if (showCurrencyDialog) {
CurrencySelectionDialog(
@@ -164,8 +180,7 @@ fun AddExpenseBottomSheet(
showCurrencyDialog = false
currency = selectedCurrency
},
selected = currency,
listOfCurrencies = listOf("PLN", "EUR", "USD")
selected = currency
)
}
@@ -177,8 +192,7 @@ fun AddExpenseBottomSheet(
category = selectedCategory
},
selected = category,
categories = categories,
settingsAndCategoryViewModel = expenseAndCategoryViewModel
categories = categories
)
}
}

View File

@@ -24,6 +24,7 @@ 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.*
import cc.n0th1ng.tripmoney.data.entity.Category
import cc.n0th1ng.tripmoney.screens.AddCategoryDialog
@@ -36,9 +37,9 @@ fun CategorySelectionDialog(
onDismiss: () -> Unit,
onCategorySelected: (Category) -> Unit,
selected: Category,
categories: List<Category>,
settingsAndCategoryViewModel: ExpenseAndCategoryViewModel
categories: List<Category>
) {
val expenseAndCategoryViewModel: ExpenseAndCategoryViewModel = hiltViewModel()
val listState = rememberLazyListState()
var showAddCategoryDialog by remember { mutableStateOf(false) }
AlertDialog(
@@ -99,7 +100,7 @@ fun CategorySelectionDialog(
AddCategoryDialog(onDismiss = {
showAddCategoryDialog = false
}, onSave = { category ->
settingsAndCategoryViewModel.save(category)
expenseAndCategoryViewModel.save(category)
showAddCategoryDialog = false
})
}

View File

@@ -14,20 +14,20 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cc.n0th1ng.tripmoney.R
import cc.n0th1ng.tripmoney.utils.Currencies
@Composable
fun CurrencySelectionDialog(
onDismiss: () -> Unit,
onCurrencySelected: (String) -> Unit,
selected: String,
listOfCurrencies: List<String>
selected: String
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.pick_currency)) },
text = {
Column {
listOfCurrencies.forEach { currency ->
Currencies.names().forEach { currency ->
Row(
modifier = Modifier
.fillMaxWidth()

View File

@@ -5,11 +5,13 @@ 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.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.rememberTimePickerState
import androidx.compose.runtime.Composable
@@ -20,11 +22,73 @@ 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 DatePicker(
dateTime: LocalDate = LocalDate.now(),
onDismiss: () -> Unit,
onConfirm: (LocalDate) -> Unit
) {
val datePickerState =
rememberDatePickerState(initialSelectedDateMillis = dateTime.toEpochMilli())
DatePickerDialog(
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = {
val selectedMillis = datePickerState.selectedDateMillis
if (selectedMillis != null) {
val selectedDate = Instant.ofEpochMilli(selectedMillis)
.atZone(ZoneId.systemDefault())
.toLocalDate()
onConfirm(selectedDate)
}
}) {
Text("OK")
}
},
dismissButton = {
TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) }
}
) {
DatePicker(state = datePickerState)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimePicker(onDismiss: () -> Unit, onConfirm: (TimePickerState) -> Unit) {
val currentTime = Calendar.getInstance()
val timePickerState = rememberTimePickerState(
initialHour = currentTime.get(Calendar.HOUR_OF_DAY),
initialMinute = currentTime.get(Calendar.MINUTE),
is24Hour = true
)
AlertDialog(
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = { onConfirm(timePickerState) }) {
Text("OK")
}
},
dismissButton = {
TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) }
},
text = { TimePicker(state = timePickerState) }
)
}
@Composable
@RequiresApi(Build.VERSION_CODES.O)
@@ -33,76 +97,34 @@ fun DateTimePicker(
dateTime: LocalDateTime = LocalDateTime.now(),
onChange: (LocalDateTime) -> Unit
) {
val datePickerState =
rememberDatePickerState(initialSelectedDateMillis = dateTime.toEpochMilli())
val timePickerState = rememberTimePickerState(
initialHour = dateTime.hour,
initialMinute = dateTime.minute
)
var showDatePicker by remember { mutableStateOf(false) }
var showDatePicker by remember { mutableStateOf(true) }
var showTimePicker by remember { mutableStateOf(false) }
val formatter = DateTimeFormatter.ofPattern("dd.MM HH:mm")
OutlinedButton(onClick = { showDatePicker = true }) {
Text(text = dateTime.format(formatter), fontSize = 17.sp)
}
var date by remember { mutableStateOf(dateTime.toLocalDate()) }
if (showDatePicker) {
DatePickerDialog(
onDismissRequest = { showDatePicker = false },
confirmButton = {
TextButton(onClick = {
showDatePicker = false
val selectedMillis = datePickerState.selectedDateMillis
if (selectedMillis != null) {
val selectedDate = Instant.ofEpochMilli(selectedMillis)
.atZone(ZoneId.systemDefault())
.toLocalDate()
// open time picker next
showTimePicker = true
onChange(
LocalDateTime.of(
selectedDate,
dateTime.toLocalTime()
)
)
}
}) {
Text("OK")
}
},
dismissButton = {
TextButton(onClick = {
showDatePicker = false
}) { Text(stringResource(string.cancel)) }
}
) {
DatePicker(state = datePickerState)
}
DatePicker(onDismiss = { showDatePicker = false }, onConfirm = { newDate ->
date = newDate
})
}
if (showTimePicker) {
AlertDialog(
onDismissRequest = { showTimePicker = false },
confirmButton = {
TextButton(onClick = {
showTimePicker = false
val newTime = LocalTime.of(timePickerState.hour, timePickerState.minute)
onChange(LocalDateTime.of(dateTime.toLocalDate(), newTime))
}) {
Text("OK")
}
},
dismissButton = {
TextButton(onClick = { showTimePicker = false }) { Text(stringResource(string.cancel)) }
},
text = { TimePicker(state = timePickerState) }
)
TimePicker(onDismiss = {
showTimePicker = false
showDatePicker = true
}, onConfirm = { timePickerState ->
showTimePicker = false
showDatePicker = true
val newTime = LocalTime.of(timePickerState.hour, timePickerState.minute)
onChange(LocalDateTime.of(date, newTime))
})
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun LocalDateTime.toEpochMilli(): Long =
this.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
this.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
@RequiresApi(Build.VERSION_CODES.O)
fun LocalDate.toEpochMilli(): Long =
this.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()

View File

@@ -2,7 +2,6 @@ package cc.n0th1ng.tripmoney.screens.listexpense
import android.annotation.SuppressLint
import android.os.Build
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -53,7 +52,7 @@ import androidx.compose.ui.unit.sp
import androidx.core.graphics.toColorInt
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.paging.compose.collectAsLazyPagingItems
import cc.n0th1ng.tripmoney.R.*
import cc.n0th1ng.tripmoney.R.string
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
import cc.n0th1ng.tripmoney.screens.addexpense.AddExpenseBottomSheet
@@ -61,7 +60,6 @@ import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.getValue
@OptIn(ExperimentalMaterial3Api::class)
@@ -145,9 +143,7 @@ fun ListExpenseScreen() {
expenseDtoToEdit = null
showBottomSheet = false
},
settingsViewModel = settingsViewModel,
categories = categories,
expenseAndCategoryViewModel = expenseAndCategoryViewModel,
expenseDtoToEdit = expenseDtoToEdit
)
}
@@ -287,8 +283,8 @@ fun ExpenseCard(expenseDto: ExpenseDto, onClick: (ExpenseDto) -> Unit) {
.fillMaxHeight()
.padding(vertical = 8.dp)
) {
Column(
) {
Column()
{
Text(
text = expenseDto.category.name,
fontWeight = FontWeight.Bold,

View File

@@ -0,0 +1,125 @@
package cc.n0th1ng.tripmoney.screens.trippicker
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
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.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import cc.n0th1ng.tripmoney.R
import cc.n0th1ng.tripmoney.R.string
import cc.n0th1ng.tripmoney.data.entity.Trip
import cc.n0th1ng.tripmoney.screens.addexpense.CurrencyButton
import cc.n0th1ng.tripmoney.screens.addexpense.isDoubleTwoDigitsAboveZero
import cc.n0th1ng.tripmoney.screens.listexpense.CurrencySelectionDialog
import cc.n0th1ng.tripmoney.screens.listexpense.DatePicker
import cc.n0th1ng.tripmoney.screens.listexpense.DateTimePicker
import cc.n0th1ng.tripmoney.utils.Currencies
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddTripBottomSheet(onDismiss: () -> Unit, onSave: (Trip) -> Unit, tripToEdit: Trip?) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var name by remember { mutableStateOf(tripToEdit?.name ?: "") }
var startDate by remember {
mutableStateOf(
LocalDate.parse(tripToEdit?.startDate ?: LocalDate.now().toString())
)
}
var showCurrencyDialog by remember { mutableStateOf(false) }
var showDatePicker by remember { mutableStateOf(false) }
var currency by remember { mutableStateOf(tripToEdit?.currency ?: Currencies.default().name) }
var enableSave by remember { mutableStateOf(tripToEdit != null) }
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.Start
) {
NameInput(name = name, onTextChange = { newText ->
name = newText
enableSave = !name.isEmpty()
})
CurrencyButton(onClick = {showCurrencyDialog = true}, currency)
OutlinedButton(onClick = { showDatePicker = true }) {
Text(
text = startDate.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")),
fontSize = 17.sp
)
}
OutlinedButton(
enabled = enableSave,
onClick = {
onSave(Trip(name = name, startDate = startDate.toString(), currency = currency))
}) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = stringResource(R.string.save)
)
}
}
}
if (showCurrencyDialog) {
CurrencySelectionDialog(
onDismiss = { showCurrencyDialog = false },
onCurrencySelected = { selectedCurrency ->
showCurrencyDialog = false
currency = selectedCurrency
},
selected = currency
)
}
if (showDatePicker) {
DatePicker(startDate, onDismiss = {showDatePicker = false}, onConfirm = { newDate ->
startDate = newDate
showDatePicker = false
})
}
}
@Composable
fun NameInput(name: String, onTextChange: (String) -> Unit) {
var text by remember { mutableStateOf(name) }
OutlinedTextField(
label = { Text(stringResource(R.string.name)) }, value = name, onValueChange = { newText ->
text = newText
onTextChange(text)
}, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}

View File

@@ -1,5 +1,9 @@
package cc.n0th1ng.tripmoney.screens.trippicker
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.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -7,74 +11,158 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.runtime.Composable
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import cc.n0th1ng.tripmoney.data.entity.Trip
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExtendedFloatingActionButton
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.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.layout.ModifierLocalBeyondBoundsLayout
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
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.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.viewmodel.SettingsViewModel
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
@RequiresApi(Build.VERSION_CODES.O)
@Composable
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
fun TripPickerScreen(
navController: NavController
) {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val tripViewModel: TripViewModel = hiltViewModel()
var showBottomSheet by remember { mutableStateOf(false) }
val trips: LazyPagingItems<Trip> = tripViewModel.getTrips().collectAsLazyPagingItems()
val currentTripId by settingsViewModel.currentTrip.collectAsState()
LazyColumn(
modifier = Modifier
.padding(horizontal = 15.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
items(trips.itemCount, trips.itemKey { it.id }) { i ->
Spacer(Modifier.height(10.dp))
val trip = trips[i]
if (trip != null) {
TripCard(trip, currentTripId == trip.id, onClick = {
settingsViewModel.setCurrentTrip(trip.id)
navController.navigate(Screens.LIST_EXPENSE)
})
Scaffold(floatingActionButtonPosition = FabPosition.EndOverlay, floatingActionButton = {
FloatingActionButton(
onClick = { showBottomSheet = true }) {
Icon(Icons.Filled.Add, stringResource(string.add_trip))
}
}) { paddingValues ->
LazyColumn(
modifier = Modifier
.padding(horizontal = 15.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
items(trips.itemCount, trips.itemKey { it.id }) { i ->
Spacer(Modifier.height(10.dp))
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)
}
Spacer(Modifier.height(10.dp))
}
Spacer(Modifier.height(10.dp))
}
if (showBottomSheet) {
AddTripBottomSheet(
onDismiss = {
showBottomSheet = false
},
onSave = { trip ->
tripViewModel.save(trip)
showBottomSheet = false
},
null
)
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun TripCard(trip: Trip, isSelected: Boolean, onClick: () -> Unit) {
fun SwipeToDeleteTripCard(
trip: Trip, onDelete: (Trip) -> Unit, onClick: (Trip) -> Unit, isSelected: Boolean
) {
var dismissed by remember { mutableStateOf(false) }
var showDialog by remember { mutableStateOf(false) }
if (!dismissed) {
val dismissState = rememberSwipeToDismissBoxState(
confirmValueChange = { dismissValue ->
if (dismissValue == SwipeToDismissBoxValue.EndToStart) {
showDialog = true
false
} else {
false
}
})
if (showDialog) {
DeleteConfirmationDialog(onConfirm = {
showDialog = false
dismissed = true
onDelete(trip)
}, onCancel = { showDialog = false })
}
SwipeToDismissBox(
modifier = Modifier.alpha(if (isSelected) 1.0f else 0.7f),
state = dismissState,
enableDismissFromStartToEnd = false,
backgroundContent = {
Box(
Modifier
.clip(CardDefaults.elevatedShape)
.fillMaxSize()
.background(MaterialTheme.colorScheme.onError)
.padding(horizontal = 20.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(Icons.Default.Delete, contentDescription = stringResource(string.delete))
}
}) {
TripCard(trip, isSelected, onClick = onClick)
}
}
}
@Composable
fun TripCard(trip: Trip, isSelected: Boolean, onClick: (Trip) -> Unit) {
ElevatedCard(
modifier = Modifier
.height(100.dp)
.clickable(true, onClick = onClick)
.alpha(if (isSelected) 1.0f else 0.7f),
.clickable(true, onClick = { onClick(trip) }),
elevation = CardDefaults.cardElevation(defaultElevation = if (isSelected) 7.dp else 0.dp)
) {
Row(
@@ -83,8 +171,7 @@ fun TripCard(trip: Trip, isSelected: Boolean, onClick: () -> Unit) {
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.padding(16.dp)
modifier = Modifier.padding(16.dp)
) {
Text(fontSize = 25.sp, fontWeight = FontWeight.SemiBold, text = trip.name)
Text(trip.startDate)
@@ -96,6 +183,5 @@ fun TripCard(trip: Trip, isSelected: Boolean, onClick: () -> Unit) {
fontWeight = FontWeight.SemiBold
)
}
}
}

View File

@@ -0,0 +1,16 @@
package cc.n0th1ng.tripmoney.utils
enum class Currencies {
PLN,
EUR,
USD;
companion object {
fun default(): Currencies {
return PLN
}
fun names(): List<String> {
return Currencies.entries.map { it.name }
}
}
}

View File

@@ -5,10 +5,12 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.Trip
import cc.n0th1ng.tripmoney.data.repository.TripRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
@@ -16,4 +18,16 @@ class TripViewModel @Inject constructor(private val repository: TripRepository)
fun getTrips(): Flow<PagingData<Trip>> = repository.getTrips().cachedIn(viewModelScope)
fun delete(trip: Trip) {
viewModelScope.launch {
repository.delete(trip)
}
}
fun save(trip: Trip) {
viewModelScope.launch {
repository.save(trip)
}
}
}