Compare commits
3 Commits
f83bf62655
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae5394aa59 | ||
|
|
dae0212cf9 | ||
|
|
270ff4fa07 |
@@ -15,7 +15,7 @@ android {
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId = "cc.n0th1ng.tripmoney"
|
||||
minSdk = 24
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
@@ -44,6 +44,7 @@ import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDate
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -57,7 +58,6 @@ class MainActivity : ComponentActivity() {
|
||||
NavigationDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +111,16 @@ fun NavigationDrawer() {
|
||||
startDestination = if (currentTripId == -1) Screens.TRIP_PICKER else Screens.LIST_EXPENSE,
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
composable(Screens.LIST_EXPENSE+"?dateToScroll={dateToScroll}",
|
||||
arguments = listOf(navArgument("dateToScroll"){defaultValue = ""})) {
|
||||
backStackEntry ->
|
||||
composable(
|
||||
Screens.LIST_EXPENSE + "?dateToScroll={dateToScroll}",
|
||||
arguments = listOf(navArgument("dateToScroll") { defaultValue = "" })
|
||||
) { backStackEntry ->
|
||||
ListExpenseScreen(
|
||||
filter = filter, search = search,
|
||||
initialAutoOpen = shouldTriggerAutoOpen,
|
||||
onAutoOpenConsumed = { hasHandledStartupOpen = true },
|
||||
dateToScroll = backStackEntry.arguments?.getString("dateToScroll")?: "")
|
||||
dateToScroll = backStackEntry.arguments?.getString("dateToScroll") ?: ""
|
||||
)
|
||||
}
|
||||
composable(Screens.TRIP_PICKER) {
|
||||
TripPickerScreen(navController)
|
||||
@@ -139,7 +141,9 @@ fun NavigationDrawer() {
|
||||
|
||||
data class Filter(
|
||||
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 {
|
||||
return this.copy(categories = categories + category)
|
||||
@@ -153,11 +157,19 @@ data class Filter(
|
||||
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 {
|
||||
return this.copy(categories = categories - category)
|
||||
}
|
||||
|
||||
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()
|
||||
),
|
||||
Category(
|
||||
name = "Jedzenie", icon = Icons.RESTAURANT, color = colors.random()
|
||||
name = "Restaurants", icon = Icons.RESTAURANT, color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Transport", icon = Icons.FLIGHT, color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Rozrywka", icon = Icons.ATTRACTION, color = colors.random()
|
||||
name = "Entertainment", icon = Icons.ATTRACTION, color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Zakupy", icon = Icons.GROCERIES, color = colors.random()
|
||||
name = "Groceries", icon = Icons.GROCERIES, color = colors.random()
|
||||
),
|
||||
Category(
|
||||
name = "Zakupy1", icon = Icons.GROCERIES, color = colors.random()
|
||||
|
||||
@@ -40,6 +40,12 @@ interface ExpenseDao {
|
||||
AND (
|
||||
: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
|
||||
"""
|
||||
@@ -50,7 +56,9 @@ interface ExpenseDao {
|
||||
categoryIds: List<Int>,
|
||||
categoriesEmpty: Boolean,
|
||||
startAmount: Double?,
|
||||
endAmount: Double?
|
||||
endAmount: Double?,
|
||||
startDate: Long?,
|
||||
endDate: Long?
|
||||
): PagingSource<Int, ExpenseDto>
|
||||
|
||||
@Transaction
|
||||
@@ -74,7 +82,12 @@ interface ExpenseDao {
|
||||
AND (
|
||||
: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
|
||||
"""
|
||||
)
|
||||
@@ -84,7 +97,9 @@ interface ExpenseDao {
|
||||
categoryIds: List<Int>,
|
||||
categoriesEmpty: Boolean,
|
||||
startAmount: Double?,
|
||||
endAmount: Double?
|
||||
endAmount: Double?,
|
||||
startDate: Long?,
|
||||
endDate: Long?
|
||||
): Flow<List<ExpenseDto>>
|
||||
|
||||
@Query(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.paging.Pager
|
||||
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.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.toEpochMilli
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExpenseRepository @Inject constructor(
|
||||
@@ -49,9 +49,10 @@ class ExpenseRepository @Inject constructor(
|
||||
categoryIds = categoryIds,
|
||||
categoriesEmpty = categoryIds.isEmpty(),
|
||||
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
|
||||
}
|
||||
@@ -62,18 +63,18 @@ class ExpenseRepository @Inject constructor(
|
||||
filter: Filter = Filter()
|
||||
): Flow<List<ExpenseDto>> {
|
||||
val categoryIds = filter.categories.map { it.id }
|
||||
|
||||
return expenseDao.expenseDto(
|
||||
tripId = tripId,
|
||||
search = search.takeIf { it.isNotBlank() },
|
||||
categoryIds = categoryIds,
|
||||
categoriesEmpty = categoryIds.isEmpty(),
|
||||
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) {
|
||||
val expenses = getExpensesDto(tripId).first()
|
||||
expenses.forEach { expenseDto ->
|
||||
|
||||
@@ -2,8 +2,6 @@ package cc.n0th1ng.tripmoney.navigation
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
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.padding
|
||||
@@ -12,11 +10,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
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.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -44,6 +38,7 @@ import cc.n0th1ng.tripmoney.Filter
|
||||
import cc.n0th1ng.tripmoney.R
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.categoriesToPreview
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.FilterDialog
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import com.composables.icons.materialsymbols.outlined.R.drawable
|
||||
@@ -159,71 +154,9 @@ fun TopBar(
|
||||
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
|
||||
fun AmountTextField(label: String, onValueChange: (String) -> Unit, value: String) {
|
||||
var value by remember { mutableStateOf(value) }
|
||||
@@ -276,24 +209,4 @@ fun PreviewTopBar() {
|
||||
filter = Filter()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DatePicker
|
||||
@@ -21,8 +19,7 @@ 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.tooling.preview.Preview
|
||||
import cc.n0th1ng.tripmoney.R.*
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.AllPreviews
|
||||
import java.time.Instant
|
||||
@@ -31,7 +28,6 @@ import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.ZoneId
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DateRangePicker(
|
||||
@@ -75,7 +71,6 @@ fun DateRangePicker(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DatePicker(
|
||||
@@ -117,7 +112,6 @@ fun DatePicker(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TimePicker(
|
||||
@@ -147,7 +141,6 @@ fun TimePicker(
|
||||
}
|
||||
|
||||
@Composable
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun DateTimePicker(
|
||||
dateTime: LocalDateTime = LocalDateTime.now(),
|
||||
@@ -181,15 +174,12 @@ fun DateTimePicker(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun LocalDateTime.toEpochMilli(): Long =
|
||||
this.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
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
|
||||
@Composable
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -85,7 +83,6 @@ import java.time.format.DateTimeFormatter
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun ListExpenseScreen(
|
||||
filter: Filter,
|
||||
@@ -124,7 +121,6 @@ fun ListExpenseScreen(
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun ListExpenseScreen(
|
||||
currentTrip: Trip?,
|
||||
@@ -286,7 +282,6 @@ fun ListExpenseScreen(
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
||||
Row(
|
||||
@@ -329,7 +324,6 @@ fun CustomDivider(date: LocalDate, sum: Double, currency: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun SwipeToDeleteExpenseCard(
|
||||
expenseDto: ExpenseDto,
|
||||
@@ -422,7 +416,6 @@ fun DeleteConfirmationDialog(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun ExpenseCard(
|
||||
expenseDto: ExpenseDto,
|
||||
@@ -523,7 +516,6 @@ fun ExpenseCard(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewListExpenseScreen() {
|
||||
@@ -550,7 +542,6 @@ fun PreviewListExpenseScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewListExpenseScreenWithoutExpenses() {
|
||||
@@ -577,7 +568,6 @@ fun PreviewListExpenseScreenWithoutExpenses() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AllPreviews
|
||||
@Composable
|
||||
fun PreviewListExpenseScreenWithoutTrip() {
|
||||
@@ -609,7 +599,6 @@ fun PreviewDeleteConfirmationDialog() {
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun sampleExpenseDtoWithConvertedAmountList(): List<ExpenseListItemUi> {
|
||||
val sampleCategories = listOf(
|
||||
Category(
|
||||
|
||||
@@ -88,7 +88,6 @@ fun StatisticsScreen(navController: NavController) {
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun StatisticsScreen(
|
||||
summaryPerCategoryList: List<SummaryPerCategory>,
|
||||
@@ -124,7 +123,7 @@ fun StatisticsScreen(
|
||||
modifier = Modifier.heightIn(max = 300.dp),
|
||||
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
|
||||
|
||||
val colors: List<String> = listOf(
|
||||
"#af1b3f",
|
||||
"#083D77",
|
||||
"#5998c5",
|
||||
"#f7934c",
|
||||
"#ec0b43",
|
||||
"#87A330",
|
||||
"#6F8AB7",
|
||||
"#F26CA7",
|
||||
"#5E4AE3",
|
||||
"#0B7189",
|
||||
"#2A7F62",
|
||||
"#0B7189"
|
||||
"#5998c5",
|
||||
"#5E4AE3",
|
||||
"#6F8AB7",
|
||||
"#87A330",
|
||||
"#F26CA7",
|
||||
"#af1b3f",
|
||||
"#ec0b43",
|
||||
"#f7934c"
|
||||
)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package cc.n0th1ng.tripmoney.utils
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun LocalDate.pretty(): String {
|
||||
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),
|
||||
GENERAL(R.drawable.materialsymbols_ic_shoppingmode_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>> =
|
||||
expenseRepo.getExpensesDto(tripId, search, filter)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun save(expense: Expense, trip: Trip, onComplete: (Int) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
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) {
|
||||
file.writer().use { writer ->
|
||||
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>> {
|
||||
return getExpensesDto(tripId, search, filter)
|
||||
.map { expenses ->
|
||||
@@ -164,14 +161,12 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun getSummaryAmount(tripId: Int): Flow<Double> {
|
||||
return getExpensesDto(tripId).map { list ->
|
||||
list.sumOf { it.expense.amount * it.expense.rate }
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun getSummaryPerCategory(tripId: Int): Flow<List<SummaryPerCategory>> {
|
||||
val tripFlow = tripRepo.getTrip(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>> {
|
||||
val tripFlow = tripRepo.getTrip(tripId)
|
||||
val expensesFlow = getExpensesDto(tripId)
|
||||
@@ -220,14 +214,12 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun clearOldRates() {
|
||||
viewModelScope.launch {
|
||||
exchangeRateRepository.clearOldRates()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
sealed class ExpenseListItemUi {
|
||||
data class Item(val expenseDto: ExpenseDto) : ExpenseListItemUi()
|
||||
data class Header(val date: LocalDate, val sum: Double, val currency: String) :
|
||||
|
||||
Reference in New Issue
Block a user