Compare commits
3 Commits
f83bf62655
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae5394aa59 | ||
|
|
dae0212cf9 | ||
|
|
270ff4fa07 |
@@ -15,7 +15,7 @@ android {
|
|||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "cc.n0th1ng.tripmoney"
|
applicationId = "cc.n0th1ng.tripmoney"
|
||||||
minSdk = 24
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
|||||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@@ -57,7 +58,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
NavigationDrawer()
|
NavigationDrawer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +111,16 @@ fun NavigationDrawer() {
|
|||||||
startDestination = if (currentTripId == -1) Screens.TRIP_PICKER else Screens.LIST_EXPENSE,
|
startDestination = if (currentTripId == -1) Screens.TRIP_PICKER else Screens.LIST_EXPENSE,
|
||||||
modifier = Modifier.padding(innerPadding)
|
modifier = Modifier.padding(innerPadding)
|
||||||
) {
|
) {
|
||||||
composable(Screens.LIST_EXPENSE+"?dateToScroll={dateToScroll}",
|
composable(
|
||||||
arguments = listOf(navArgument("dateToScroll"){defaultValue = ""})) {
|
Screens.LIST_EXPENSE + "?dateToScroll={dateToScroll}",
|
||||||
backStackEntry ->
|
arguments = listOf(navArgument("dateToScroll") { defaultValue = "" })
|
||||||
|
) { backStackEntry ->
|
||||||
ListExpenseScreen(
|
ListExpenseScreen(
|
||||||
filter = filter, search = search,
|
filter = filter, search = search,
|
||||||
initialAutoOpen = shouldTriggerAutoOpen,
|
initialAutoOpen = shouldTriggerAutoOpen,
|
||||||
onAutoOpenConsumed = { hasHandledStartupOpen = true },
|
onAutoOpenConsumed = { hasHandledStartupOpen = true },
|
||||||
dateToScroll = backStackEntry.arguments?.getString("dateToScroll")?: "")
|
dateToScroll = backStackEntry.arguments?.getString("dateToScroll") ?: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(Screens.TRIP_PICKER) {
|
composable(Screens.TRIP_PICKER) {
|
||||||
TripPickerScreen(navController)
|
TripPickerScreen(navController)
|
||||||
@@ -139,7 +141,9 @@ fun NavigationDrawer() {
|
|||||||
|
|
||||||
data class Filter(
|
data class Filter(
|
||||||
val categories: List<Category> = emptyList(), val startAmount: Double = 0.0,
|
val categories: List<Category> = emptyList(), val startAmount: Double = 0.0,
|
||||||
val endAmount: Double = Double.MAX_VALUE
|
val endAmount: Double = Double.MAX_VALUE,
|
||||||
|
val startDate: LocalDate = LocalDate.MIN,
|
||||||
|
val endDate: LocalDate = LocalDate.MAX
|
||||||
) {
|
) {
|
||||||
fun with(category: Category): Filter {
|
fun with(category: Category): Filter {
|
||||||
return this.copy(categories = categories + category)
|
return this.copy(categories = categories + category)
|
||||||
@@ -153,11 +157,19 @@ data class Filter(
|
|||||||
return this.copy(endAmount = amount)
|
return this.copy(endAmount = amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withStartDate(date: LocalDate): Filter {
|
||||||
|
return this.copy(startDate = date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withEndDate(date: LocalDate): Filter {
|
||||||
|
return this.copy(endDate = date)
|
||||||
|
}
|
||||||
|
|
||||||
fun without(category: Category): Filter {
|
fun without(category: Category): Filter {
|
||||||
return this.copy(categories = categories - category)
|
return this.copy(categories = categories - category)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDefault(): Boolean {
|
fun isDefault(): Boolean {
|
||||||
return this.categories.isEmpty() && startAmount == 0.0 && endAmount == Double.MAX_VALUE
|
return this.categories.isEmpty() && startAmount == 0.0 && endAmount == Double.MAX_VALUE && startDate == LocalDate.MIN && endDate == LocalDate.MAX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,16 +153,16 @@ private class DatabasePrepopulator(
|
|||||||
name = "Hotel", icon = Icons.HOTEL, color = colors.random()
|
name = "Hotel", icon = Icons.HOTEL, color = colors.random()
|
||||||
),
|
),
|
||||||
Category(
|
Category(
|
||||||
name = "Jedzenie", icon = Icons.RESTAURANT, color = colors.random()
|
name = "Restaurants", icon = Icons.RESTAURANT, color = colors.random()
|
||||||
),
|
),
|
||||||
Category(
|
Category(
|
||||||
name = "Transport", icon = Icons.FLIGHT, color = colors.random()
|
name = "Transport", icon = Icons.FLIGHT, color = colors.random()
|
||||||
),
|
),
|
||||||
Category(
|
Category(
|
||||||
name = "Rozrywka", icon = Icons.ATTRACTION, color = colors.random()
|
name = "Entertainment", icon = Icons.ATTRACTION, color = colors.random()
|
||||||
),
|
),
|
||||||
Category(
|
Category(
|
||||||
name = "Zakupy", icon = Icons.GROCERIES, color = colors.random()
|
name = "Groceries", icon = Icons.GROCERIES, color = colors.random()
|
||||||
),
|
),
|
||||||
Category(
|
Category(
|
||||||
name = "Zakupy1", icon = Icons.GROCERIES, color = colors.random()
|
name = "Zakupy1", icon = Icons.GROCERIES, color = colors.random()
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ interface ExpenseDao {
|
|||||||
AND (
|
AND (
|
||||||
:endAmount IS NULL OR expense.amount <= :endAmount
|
:endAmount IS NULL OR expense.amount <= :endAmount
|
||||||
)
|
)
|
||||||
|
AND (
|
||||||
|
:startDate IS NULL OR expense.datetime >= :startDate
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
:endDate IS NULL OR expense.datetime <= :endDate
|
||||||
|
)
|
||||||
|
|
||||||
ORDER BY expense.datetime DESC
|
ORDER BY expense.datetime DESC
|
||||||
"""
|
"""
|
||||||
@@ -50,7 +56,9 @@ interface ExpenseDao {
|
|||||||
categoryIds: List<Int>,
|
categoryIds: List<Int>,
|
||||||
categoriesEmpty: Boolean,
|
categoriesEmpty: Boolean,
|
||||||
startAmount: Double?,
|
startAmount: Double?,
|
||||||
endAmount: Double?
|
endAmount: Double?,
|
||||||
|
startDate: Long?,
|
||||||
|
endDate: Long?
|
||||||
): PagingSource<Int, ExpenseDto>
|
): PagingSource<Int, ExpenseDto>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@@ -74,7 +82,12 @@ interface ExpenseDao {
|
|||||||
AND (
|
AND (
|
||||||
:endAmount IS NULL OR expense.amount <= :endAmount
|
:endAmount IS NULL OR expense.amount <= :endAmount
|
||||||
)
|
)
|
||||||
|
AND (
|
||||||
|
:startDate IS NULL OR expense.datetime >= :startDate
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
:endDate IS NULL OR expense.datetime <= :endDate
|
||||||
|
)
|
||||||
ORDER BY expense.datetime DESC
|
ORDER BY expense.datetime DESC
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@@ -84,7 +97,9 @@ interface ExpenseDao {
|
|||||||
categoryIds: List<Int>,
|
categoryIds: List<Int>,
|
||||||
categoriesEmpty: Boolean,
|
categoriesEmpty: Boolean,
|
||||||
startAmount: Double?,
|
startAmount: Double?,
|
||||||
endAmount: Double?
|
endAmount: Double?,
|
||||||
|
startDate: Long?,
|
||||||
|
endDate: Long?
|
||||||
): Flow<List<ExpenseDto>>
|
): Flow<List<ExpenseDto>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package cc.n0th1ng.tripmoney.data.repository
|
package cc.n0th1ng.tripmoney.data.repository
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
@@ -10,9 +8,11 @@ import cc.n0th1ng.tripmoney.Filter
|
|||||||
import cc.n0th1ng.tripmoney.data.dao.ExpenseDao
|
import cc.n0th1ng.tripmoney.data.dao.ExpenseDao
|
||||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||||
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
||||||
|
import cc.n0th1ng.tripmoney.screens.listexpense.toEpochMilli
|
||||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExpenseRepository @Inject constructor(
|
class ExpenseRepository @Inject constructor(
|
||||||
@@ -49,9 +49,10 @@ class ExpenseRepository @Inject constructor(
|
|||||||
categoryIds = categoryIds,
|
categoryIds = categoryIds,
|
||||||
categoriesEmpty = categoryIds.isEmpty(),
|
categoriesEmpty = categoryIds.isEmpty(),
|
||||||
startAmount = filter.startAmount,
|
startAmount = filter.startAmount,
|
||||||
endAmount = filter.endAmount
|
endAmount = filter.endAmount,
|
||||||
|
startDate = if(filter.startDate == LocalDate.MIN) null else filter.startDate.toEpochMilli(),
|
||||||
|
endDate = if(filter.endDate == LocalDate.MAX) null else filter.endDate.plusDays(1).toEpochMilli(),
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
).flow
|
).flow
|
||||||
}
|
}
|
||||||
@@ -62,18 +63,18 @@ class ExpenseRepository @Inject constructor(
|
|||||||
filter: Filter = Filter()
|
filter: Filter = Filter()
|
||||||
): Flow<List<ExpenseDto>> {
|
): Flow<List<ExpenseDto>> {
|
||||||
val categoryIds = filter.categories.map { it.id }
|
val categoryIds = filter.categories.map { it.id }
|
||||||
|
|
||||||
return expenseDao.expenseDto(
|
return expenseDao.expenseDto(
|
||||||
tripId = tripId,
|
tripId = tripId,
|
||||||
search = search.takeIf { it.isNotBlank() },
|
search = search.takeIf { it.isNotBlank() },
|
||||||
categoryIds = categoryIds,
|
categoryIds = categoryIds,
|
||||||
categoriesEmpty = categoryIds.isEmpty(),
|
categoriesEmpty = categoryIds.isEmpty(),
|
||||||
startAmount = filter.startAmount,
|
startAmount = filter.startAmount,
|
||||||
endAmount = filter.endAmount
|
endAmount = filter.endAmount,
|
||||||
|
startDate = if(filter.startDate == LocalDate.MIN) null else filter.startDate.toEpochMilli(),
|
||||||
|
endDate = if(filter.endDate == LocalDate.MAX) null else filter.endDate.plusDays(1).toEpochMilli(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
suspend fun recalculateTripExpenses(tripId: Int) {
|
suspend fun recalculateTripExpenses(tripId: Int) {
|
||||||
val expenses = getExpensesDto(tripId).first()
|
val expenses = getExpensesDto(tripId).first()
|
||||||
expenses.forEach { expenseDto ->
|
expenses.forEach { expenseDto ->
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package cc.n0th1ng.tripmoney.navigation
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -12,11 +10,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -44,6 +38,7 @@ import cc.n0th1ng.tripmoney.Filter
|
|||||||
import cc.n0th1ng.tripmoney.R
|
import cc.n0th1ng.tripmoney.R
|
||||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||||
import cc.n0th1ng.tripmoney.screens.addexpense.categoriesToPreview
|
import cc.n0th1ng.tripmoney.screens.addexpense.categoriesToPreview
|
||||||
|
import cc.n0th1ng.tripmoney.screens.listexpense.FilterDialog
|
||||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||||
import com.composables.icons.materialsymbols.outlined.R.drawable
|
import com.composables.icons.materialsymbols.outlined.R.drawable
|
||||||
@@ -159,71 +154,9 @@ fun TopBar(
|
|||||||
showFilter = false
|
showFilter = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun FilterDialog(
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onSave: (Filter) -> Unit,
|
|
||||||
onClear: () -> Unit,
|
|
||||||
categories: List<Category>,
|
|
||||||
filter: Filter
|
|
||||||
) {
|
|
||||||
var filter by remember { mutableStateOf(filter) }
|
|
||||||
var fromAmountString by remember { mutableStateOf(filter.startAmount.toString()) }
|
|
||||||
var toAmountString by remember { mutableStateOf(filter.endAmount.toString()) }
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
dismissButton = {
|
|
||||||
Button(
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary),
|
|
||||||
enabled = true,
|
|
||||||
onClick = onClear
|
|
||||||
) { Text(stringResource(R.string.clear)) }
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
Button(
|
|
||||||
enabled = true,
|
|
||||||
onClick = {
|
|
||||||
onSave(
|
|
||||||
filter.withStartAmount(fromAmountString.safeToDouble())
|
|
||||||
.withEndAmount(toAmountString.safeToDouble())
|
|
||||||
)
|
|
||||||
}) { Text(stringResource(R.string.save)) }
|
|
||||||
}, title = { Text("Filter") },
|
|
||||||
text = {
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
Text(text = "Categories")
|
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(7.dp)) {
|
|
||||||
categories.forEach {
|
|
||||||
FilterChip(selected = filter.categories.contains(it), onClick = {
|
|
||||||
filter = if (filter.categories.contains(it)) {
|
|
||||||
filter.without(it)
|
|
||||||
} else {
|
|
||||||
filter.with(it)
|
|
||||||
}
|
|
||||||
}, label = {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
|
|
||||||
Icon(painterResource(it.icon.resource), contentDescription = null)
|
|
||||||
Text(text = it.name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AmountTextField(label = "from", onValueChange = { newText ->
|
|
||||||
fromAmountString = newText
|
|
||||||
}, value = fromAmountString)
|
|
||||||
AmountTextField(label = "to", onValueChange = { newText ->
|
|
||||||
toAmountString = newText
|
|
||||||
}, value = toAmountString)
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AmountTextField(label: String, onValueChange: (String) -> Unit, value: String) {
|
fun AmountTextField(label: String, onValueChange: (String) -> Unit, value: String) {
|
||||||
var value by remember { mutableStateOf(value) }
|
var value by remember { mutableStateOf(value) }
|
||||||
@@ -277,23 +210,3 @@ fun PreviewTopBar() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllPreviews
|
|
||||||
@Composable
|
|
||||||
fun PreviewFilterDialog() {
|
|
||||||
TripMoneyTheme {
|
|
||||||
FilterDialog(
|
|
||||||
onDismiss = {},
|
|
||||||
onSave = {},
|
|
||||||
categories = categoriesToPreview,
|
|
||||||
filter = Filter(),
|
|
||||||
onClear = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.safeToDouble(): Double {
|
|
||||||
if (this == "∞") return Double.MAX_VALUE
|
|
||||||
if (this.isEmpty()) return 0.0
|
|
||||||
return this.toDouble()
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package cc.n0th1ng.tripmoney.screens.listexpense
|
package cc.n0th1ng.tripmoney.screens.listexpense
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.DatePicker
|
import androidx.compose.material3.DatePicker
|
||||||
@@ -21,8 +19,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import cc.n0th1ng.tripmoney.R.string
|
||||||
import cc.n0th1ng.tripmoney.R.*
|
|
||||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -31,7 +28,6 @@ import java.time.LocalDateTime
|
|||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DateRangePicker(
|
fun DateRangePicker(
|
||||||
@@ -75,7 +71,6 @@ fun DateRangePicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DatePicker(
|
fun DatePicker(
|
||||||
@@ -117,7 +112,6 @@ fun DatePicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TimePicker(
|
fun TimePicker(
|
||||||
@@ -147,7 +141,6 @@ fun TimePicker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun DateTimePicker(
|
fun DateTimePicker(
|
||||||
dateTime: LocalDateTime = LocalDateTime.now(),
|
dateTime: LocalDateTime = LocalDateTime.now(),
|
||||||
@@ -181,15 +174,12 @@ fun DateTimePicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun LocalDateTime.toEpochMilli(): Long =
|
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 =
|
fun LocalDate.toEpochMilli(): Long =
|
||||||
this.atStartOfDay().atZone(ZoneId.of("UTC")).toInstant().toEpochMilli()
|
this.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@AllPreviews
|
@AllPreviews
|
||||||
@Composable
|
@Composable
|
||||||
fun DatePickerPreview() {
|
fun DatePickerPreview() {
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package cc.n0th1ng.tripmoney.screens.listexpense
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import cc.n0th1ng.tripmoney.Filter
|
||||||
|
import cc.n0th1ng.tripmoney.R
|
||||||
|
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||||
|
import cc.n0th1ng.tripmoney.navigation.AmountTextField
|
||||||
|
import cc.n0th1ng.tripmoney.screens.addexpense.categoriesToPreview
|
||||||
|
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||||
|
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||||
|
import cc.n0th1ng.tripmoney.utils.pretty
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FilterDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSave: (Filter) -> Unit,
|
||||||
|
onClear: () -> Unit,
|
||||||
|
categories: List<Category>,
|
||||||
|
filter: Filter
|
||||||
|
) {
|
||||||
|
var filter by remember { mutableStateOf(filter) }
|
||||||
|
var fromAmountString by remember { mutableStateOf(filter.startAmount.toString()) }
|
||||||
|
var toAmountString by remember { mutableStateOf(filter.endAmount.toString()) }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
dismissButton = {
|
||||||
|
Button(
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary),
|
||||||
|
enabled = true,
|
||||||
|
onClick = onClear
|
||||||
|
) { Text(stringResource(R.string.clear)) }
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
enabled = true,
|
||||||
|
onClick = {
|
||||||
|
onSave(filter)
|
||||||
|
}) { Text(stringResource(R.string.save)) }
|
||||||
|
}, title = { Text("Filter") },
|
||||||
|
text = {
|
||||||
|
var showDatePicker by remember { mutableStateOf(false) }
|
||||||
|
var startDate by remember {
|
||||||
|
mutableStateOf(filter.startDate)
|
||||||
|
}
|
||||||
|
var endDate by remember {
|
||||||
|
mutableStateOf(filter.endDate)
|
||||||
|
}
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
Text(text = "Categories")
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(7.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.sizeIn(maxHeight = 200.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
categories.forEach {
|
||||||
|
FilterChip(selected = filter.categories.contains(it), onClick = {
|
||||||
|
filter = if (filter.categories.contains(it)) {
|
||||||
|
filter.without(it)
|
||||||
|
} else {
|
||||||
|
filter.with(it)
|
||||||
|
}
|
||||||
|
}, label = {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
|
||||||
|
Icon(painterResource(it.icon.resource), contentDescription = null)
|
||||||
|
Text(text = it.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(1f),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
onClick = { showDatePicker = true }) {
|
||||||
|
val startDateFormatted = startDate.pretty()
|
||||||
|
val endDateFormatted = endDate.pretty()
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
if(startDate == LocalDate.MIN && endDate == LocalDate.MAX) "Show all dates" else
|
||||||
|
"$startDateFormatted - $endDateFormatted",
|
||||||
|
fontSize = 17.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AmountTextField(label = "from", onValueChange = { newText ->
|
||||||
|
fromAmountString = newText
|
||||||
|
filter = filter.withStartAmount(newText.safeToDouble())
|
||||||
|
}, value = fromAmountString)
|
||||||
|
AmountTextField(label = "to", onValueChange = { newText ->
|
||||||
|
toAmountString = newText
|
||||||
|
filter = filter.withEndAmount(newText.safeToDouble())
|
||||||
|
}, value = toAmountString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDatePicker) {
|
||||||
|
DateRangePicker(
|
||||||
|
startDate = if(startDate == LocalDate.MIN) LocalDate.now() else startDate,
|
||||||
|
endDate = if(endDate == LocalDate.MAX) LocalDate.now() else endDate,
|
||||||
|
onDismiss = { showDatePicker = false },
|
||||||
|
onConfirm = { newStartDate, newEndDate ->
|
||||||
|
startDate = newStartDate
|
||||||
|
endDate = newEndDate
|
||||||
|
filter = filter.withStartDate(startDate).withEndDate(endDate)
|
||||||
|
showDatePicker = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@AllPreviews
|
||||||
|
@Composable
|
||||||
|
fun PreviewFilterDialog() {
|
||||||
|
TripMoneyTheme {
|
||||||
|
FilterDialog(
|
||||||
|
onDismiss = {},
|
||||||
|
onSave = {},
|
||||||
|
categories = categoriesToPreview.plus(categoriesToPreview).plus(categoriesToPreview),
|
||||||
|
filter = Filter(),
|
||||||
|
onClear = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun String.safeToDouble(): Double {
|
||||||
|
if (this == "∞") return Double.MAX_VALUE
|
||||||
|
if (this.isEmpty()) return 0.0
|
||||||
|
return this.toDouble()
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package cc.n0th1ng.tripmoney.screens.listexpense
|
package cc.n0th1ng.tripmoney.screens.listexpense
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -85,7 +83,6 @@ import java.time.format.DateTimeFormatter
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ListExpenseScreen(
|
fun ListExpenseScreen(
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
@@ -124,7 +121,6 @@ fun ListExpenseScreen(
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ListExpenseScreen(
|
fun ListExpenseScreen(
|
||||||
currentTrip: Trip?,
|
currentTrip: Trip?,
|
||||||
@@ -286,7 +282,6 @@ fun ListExpenseScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
||||||
Row(
|
Row(
|
||||||
@@ -329,7 +324,6 @@ fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwipeToDeleteExpenseCard(
|
fun SwipeToDeleteExpenseCard(
|
||||||
expenseDto: ExpenseDto,
|
expenseDto: ExpenseDto,
|
||||||
@@ -422,7 +416,6 @@ fun DeleteConfirmationDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExpenseCard(
|
fun ExpenseCard(
|
||||||
expenseDto: ExpenseDto,
|
expenseDto: ExpenseDto,
|
||||||
@@ -523,7 +516,6 @@ fun ExpenseCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@AllPreviews
|
@AllPreviews
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewListExpenseScreen() {
|
fun PreviewListExpenseScreen() {
|
||||||
@@ -550,7 +542,6 @@ fun PreviewListExpenseScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@AllPreviews
|
@AllPreviews
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewListExpenseScreenWithoutExpenses() {
|
fun PreviewListExpenseScreenWithoutExpenses() {
|
||||||
@@ -577,7 +568,6 @@ fun PreviewListExpenseScreenWithoutExpenses() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@AllPreviews
|
@AllPreviews
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewListExpenseScreenWithoutTrip() {
|
fun PreviewListExpenseScreenWithoutTrip() {
|
||||||
@@ -609,7 +599,6 @@ fun PreviewDeleteConfirmationDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
private fun sampleExpenseDtoWithConvertedAmountList(): List<ExpenseListItemUi> {
|
private fun sampleExpenseDtoWithConvertedAmountList(): List<ExpenseListItemUi> {
|
||||||
val sampleCategories = listOf(
|
val sampleCategories = listOf(
|
||||||
Category(
|
Category(
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ fun StatisticsScreen(navController: NavController) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StatisticsScreen(
|
fun StatisticsScreen(
|
||||||
summaryPerCategoryList: List<SummaryPerCategory>,
|
summaryPerCategoryList: List<SummaryPerCategory>,
|
||||||
@@ -124,7 +123,7 @@ fun StatisticsScreen(
|
|||||||
modifier = Modifier.heightIn(max = 300.dp),
|
modifier = Modifier.heightIn(max = 300.dp),
|
||||||
summaryPerCategoryList = summaryPerCategoryList
|
summaryPerCategoryList = summaryPerCategoryList
|
||||||
)
|
)
|
||||||
SummaryPerDayCard(modifier = Modifier.height(300.dp), summaryPerDayList = summaryPerDayList, onDayClicked = onDayClicked)
|
SummaryPerDayCard(modifier = Modifier.height(300.dp), summaryPerDayList = summaryPerDayList.sortedBy { it.day }, onDayClicked = onDayClicked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package cc.n0th1ng.tripmoney.utils
|
package cc.n0th1ng.tripmoney.utils
|
||||||
|
|
||||||
val colors: List<String> = listOf(
|
val colors: List<String> = listOf(
|
||||||
"#af1b3f",
|
|
||||||
"#083D77",
|
"#083D77",
|
||||||
"#5998c5",
|
"#0B7189",
|
||||||
"#f7934c",
|
|
||||||
"#ec0b43",
|
|
||||||
"#87A330",
|
|
||||||
"#6F8AB7",
|
|
||||||
"#F26CA7",
|
|
||||||
"#5E4AE3",
|
|
||||||
"#2A7F62",
|
"#2A7F62",
|
||||||
"#0B7189"
|
"#5998c5",
|
||||||
|
"#5E4AE3",
|
||||||
|
"#6F8AB7",
|
||||||
|
"#87A330",
|
||||||
|
"#F26CA7",
|
||||||
|
"#af1b3f",
|
||||||
|
"#ec0b43",
|
||||||
|
"#f7934c"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
package cc.n0th1ng.tripmoney.utils
|
package cc.n0th1ng.tripmoney.utils
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun LocalDate.pretty(): String {
|
fun LocalDate.pretty(): String {
|
||||||
return this.format(DateTimeFormatter.ofPattern("dd MMM yyyy"))
|
return this.format(DateTimeFormatter.ofPattern("dd MMM yyyy"))
|
||||||
}
|
}
|
||||||
@@ -14,5 +14,10 @@ enum class Icons(@DrawableRes val resource: Int) {
|
|||||||
COFFEE(R.drawable.materialsymbols_ic_local_cafe_outlined),
|
COFFEE(R.drawable.materialsymbols_ic_local_cafe_outlined),
|
||||||
GENERAL(R.drawable.materialsymbols_ic_shoppingmode_outlined),
|
GENERAL(R.drawable.materialsymbols_ic_shoppingmode_outlined),
|
||||||
ENTERTAINMENT(R.drawable.materialsymbols_ic_theaters_outlined),
|
ENTERTAINMENT(R.drawable.materialsymbols_ic_theaters_outlined),
|
||||||
LAUNDRY(R.drawable.materialsymbols_ic_local_laundry_service_outlined)
|
LAUNDRY(R.drawable.materialsymbols_ic_local_laundry_service_outlined),
|
||||||
|
INSURANCE(R.drawable.materialsymbols_ic_health_and_safety_outlined),
|
||||||
|
SIM_DATA(R.drawable.materialsymbols_ic_sim_card_outlined),
|
||||||
|
CAR_RENTAL(R.drawable.materialsymbols_ic_directions_car_outlined),
|
||||||
|
FUEL(R.drawable.materialsymbols_ic_local_gas_station_outlined),
|
||||||
|
TOURS(R.drawable.materialsymbols_ic_tour_outlined)
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,6 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
): Flow<List<ExpenseDto>> =
|
): Flow<List<ExpenseDto>> =
|
||||||
expenseRepo.getExpensesDto(tripId, search, filter)
|
expenseRepo.getExpensesDto(tripId, search, filter)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun save(expense: Expense, trip: Trip, onComplete: (Int) -> Unit) {
|
fun save(expense: Expense, trip: Trip, onComplete: (Int) -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val rate = exchangeRateRepository.getRate(
|
val rate = exchangeRateRepository.getRate(
|
||||||
@@ -133,7 +132,6 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
suspend fun generateCSVToFile(tripId: Int, file: File) {
|
suspend fun generateCSVToFile(tripId: Int, file: File) {
|
||||||
file.writer().use { writer ->
|
file.writer().use { writer ->
|
||||||
CSVPrinter(
|
CSVPrinter(
|
||||||
@@ -153,7 +151,6 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun getDailySums(tripId: Int, search: String, filter: Filter): Flow<Map<LocalDate, Double>> {
|
fun getDailySums(tripId: Int, search: String, filter: Filter): Flow<Map<LocalDate, Double>> {
|
||||||
return getExpensesDto(tripId, search, filter)
|
return getExpensesDto(tripId, search, filter)
|
||||||
.map { expenses ->
|
.map { expenses ->
|
||||||
@@ -164,14 +161,12 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun getSummaryAmount(tripId: Int): Flow<Double> {
|
fun getSummaryAmount(tripId: Int): Flow<Double> {
|
||||||
return getExpensesDto(tripId).map { list ->
|
return getExpensesDto(tripId).map { list ->
|
||||||
list.sumOf { it.expense.amount * it.expense.rate }
|
list.sumOf { it.expense.amount * it.expense.rate }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun getSummaryPerCategory(tripId: Int): Flow<List<SummaryPerCategory>> {
|
fun getSummaryPerCategory(tripId: Int): Flow<List<SummaryPerCategory>> {
|
||||||
val tripFlow = tripRepo.getTrip(tripId)
|
val tripFlow = tripRepo.getTrip(tripId)
|
||||||
val expensesFlow = getExpensesDto(tripId)
|
val expensesFlow = getExpensesDto(tripId)
|
||||||
@@ -194,7 +189,6 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun getSummaryPerDay(tripId: Int): Flow<List<SummaryPerDay>> {
|
fun getSummaryPerDay(tripId: Int): Flow<List<SummaryPerDay>> {
|
||||||
val tripFlow = tripRepo.getTrip(tripId)
|
val tripFlow = tripRepo.getTrip(tripId)
|
||||||
val expensesFlow = getExpensesDto(tripId)
|
val expensesFlow = getExpensesDto(tripId)
|
||||||
@@ -220,14 +214,12 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
fun clearOldRates() {
|
fun clearOldRates() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
exchangeRateRepository.clearOldRates()
|
exchangeRateRepository.clearOldRates()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
sealed class ExpenseListItemUi {
|
sealed class ExpenseListItemUi {
|
||||||
data class Item(val expenseDto: ExpenseDto) : ExpenseListItemUi()
|
data class Item(val expenseDto: ExpenseDto) : ExpenseListItemUi()
|
||||||
data class Header(val date: LocalDate, val sum: Double, val currency: String) :
|
data class Header(val date: LocalDate, val sum: Double, val currency: String) :
|
||||||
|
|||||||
Reference in New Issue
Block a user