feat: add categories per day stats
This commit is contained in:
@@ -138,12 +138,12 @@ private class DatabasePrepopulator(
|
|||||||
currency = "USD"
|
currency = "USD"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// for (category in sampleCategories) {
|
for (category in sampleCategories) {
|
||||||
// categoryDao.insert(category)
|
categoryDao.insert(category)
|
||||||
// }
|
}
|
||||||
// for (expense in sampleExpenses) {
|
for (expense in sampleExpenses) {
|
||||||
// expenseDao.insert(expense)
|
expenseDao.insert(expense)
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package cc.n0th1ng.tripmoney.data.dto
|
|||||||
|
|
||||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||||
import cc.n0th1ng.tripmoney.utils.Icons
|
import java.time.LocalDate
|
||||||
|
|
||||||
data class SummaryPerCategory(
|
data class SummaryPerCategory(
|
||||||
val category: Category,
|
val category: Category,
|
||||||
@@ -11,12 +11,8 @@ data class SummaryPerCategory(
|
|||||||
val currency: Currencies
|
val currency: Currencies
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SummaryPerCategoryRaw(
|
data class SummaryPerDay(
|
||||||
val categoryId: Int,
|
val day: LocalDate,
|
||||||
val categoryName: String,
|
|
||||||
val icon: Icons,
|
|
||||||
val color: String,
|
|
||||||
val amount: Double,
|
val amount: Double,
|
||||||
val currency: String
|
val percent: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ fun ListExpenseScreen(
|
|||||||
{
|
{
|
||||||
Box {
|
Box {
|
||||||
if (items.itemCount == 0) {
|
if (items.itemCount == 0) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize().padding(10.dp), contentAlignment = Alignment.Center) {
|
||||||
val textToShow = if (currentTrip == null || currentTrip.isDummy()) {
|
val textToShow = if (currentTrip == null || currentTrip.isDummy()) {
|
||||||
stringResource(string.no_trip_picked)
|
stringResource(string.no_trip_picked)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -20,7 +19,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material3.BasicAlertDialog
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
@@ -48,7 +46,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -63,9 +60,6 @@ import cc.n0th1ng.tripmoney.utils.AllPreviews
|
|||||||
import cc.n0th1ng.tripmoney.utils.colors
|
import cc.n0th1ng.tripmoney.utils.colors
|
||||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||||
import com.composables.icons.materialsymbols.outlined.R
|
import com.composables.icons.materialsymbols.outlined.R
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import kotlin.collections.emptyList
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -159,7 +153,7 @@ fun ManageCategoriesScreen(
|
|||||||
|
|
||||||
if (itemToDelete != null) {
|
if (itemToDelete != null) {
|
||||||
DeleteConfirmationDialog(
|
DeleteConfirmationDialog(
|
||||||
bodyText = stringResource(string.delete_category_info),
|
bodyText = stringResource(string.delete_category_info).format(itemToDelete?.name),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
onDeleteCategory(itemToDelete!!)
|
onDeleteCategory(itemToDelete!!)
|
||||||
itemToDelete = null
|
itemToDelete = null
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ import android.annotation.SuppressLint
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -35,9 +39,11 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.graphics.toColorInt
|
import androidx.core.graphics.toColorInt
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
|
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
|
||||||
|
import cc.n0th1ng.tripmoney.data.dto.SummaryPerDay
|
||||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||||
@@ -49,6 +55,8 @@ import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
|||||||
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||||
import com.composables.icons.materialsymbols.outlined.R
|
import com.composables.icons.materialsymbols.outlined.R
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -60,11 +68,14 @@ fun StatisticsScreen() {
|
|||||||
val currentTrip by tripViewModel.getTrip(currentTripId).collectAsState(Trip.DUMMY)
|
val currentTrip by tripViewModel.getTrip(currentTripId).collectAsState(Trip.DUMMY)
|
||||||
val summaryPerCategoryList by expenseAndCategoryViewModel.getSummaryPerCategory(currentTripId)
|
val summaryPerCategoryList by expenseAndCategoryViewModel.getSummaryPerCategory(currentTripId)
|
||||||
.collectAsState(emptyList())
|
.collectAsState(emptyList())
|
||||||
|
val summaryPerDayList by expenseAndCategoryViewModel.getSummaryPerDay(currentTripId)
|
||||||
|
.collectAsState(emptyList())
|
||||||
val summaryAmount by expenseAndCategoryViewModel.getSummaryAmount(currentTripId)
|
val summaryAmount by expenseAndCategoryViewModel.getSummaryAmount(currentTripId)
|
||||||
.collectAsState(0.0)
|
.collectAsState(0.0)
|
||||||
val moneyLeft by expenseAndCategoryViewModel.getBudgetLeft(currentTripId).collectAsState(null)
|
val moneyLeft by expenseAndCategoryViewModel.getBudgetLeft(currentTripId).collectAsState(null)
|
||||||
StatisticsScreen(
|
StatisticsScreen(
|
||||||
summaryPerCategoryList,
|
summaryPerCategoryList,
|
||||||
|
summaryPerDayList,
|
||||||
summaryAmount,
|
summaryAmount,
|
||||||
Currencies.valueOf(currentTrip?.currency ?: Currencies.default().name),
|
Currencies.valueOf(currentTrip?.currency ?: Currencies.default().name),
|
||||||
moneyLeft
|
moneyLeft
|
||||||
@@ -75,6 +86,7 @@ fun StatisticsScreen() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun StatisticsScreen(
|
fun StatisticsScreen(
|
||||||
summaryPerCategoryList: List<SummaryPerCategory>,
|
summaryPerCategoryList: List<SummaryPerCategory>,
|
||||||
|
summaryPerDayList: List<SummaryPerDay>,
|
||||||
summaryAmount: Double,
|
summaryAmount: Double,
|
||||||
tripCurrency: Currencies,
|
tripCurrency: Currencies,
|
||||||
moneyLeft: Double?
|
moneyLeft: Double?
|
||||||
@@ -101,8 +113,11 @@ fun StatisticsScreen(
|
|||||||
iconColor = colorResource(cc.n0th1ng.tripmoney.R.color.good_green)
|
iconColor = colorResource(cc.n0th1ng.tripmoney.R.color.good_green)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SummaryPerCategoryCard(summaryPerCategoryList)
|
SummaryPerCategoryCard(
|
||||||
|
modifier = Modifier.heightIn(max = 300.dp),
|
||||||
|
summaryPerCategoryList = summaryPerCategoryList
|
||||||
|
)
|
||||||
|
SummaryPerDayCard(modifier = Modifier.height(300.dp), summaryPerDayList = summaryPerDayList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,14 +172,22 @@ fun Summary(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SummaryPerCategoryCard(summaryPerCategoryList: List<SummaryPerCategory>) {
|
fun SummaryPerCategoryCard(
|
||||||
|
summaryPerCategoryList: List<SummaryPerCategory>,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.elevatedCardColors()
|
colors = CardDefaults.elevatedCardColors()
|
||||||
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
|
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
|
||||||
) {
|
) {
|
||||||
if (summaryPerCategoryList.isEmpty()) {
|
if (summaryPerCategoryList.isEmpty()) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(10.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(cc.n0th1ng.tripmoney.R.string.no_expenses_summary),
|
text = stringResource(cc.n0th1ng.tripmoney.R.string.no_expenses_summary),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
@@ -192,6 +215,47 @@ fun SummaryPerCategoryCard(summaryPerCategoryList: List<SummaryPerCategory>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
@Composable
|
||||||
|
fun SummaryPerDayCard(modifier: Modifier = Modifier, summaryPerDayList: List<SummaryPerDay>) {
|
||||||
|
ElevatedCard(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.elevatedCardColors()
|
||||||
|
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
) {
|
||||||
|
if (summaryPerDayList.isEmpty()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(10.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(cc.n0th1ng.tripmoney.R.string.no_expenses_summary),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(15.dp)
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
summaryPerDayList.forEach {
|
||||||
|
DayCard(
|
||||||
|
summaryPerDay = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryCard(modifier: Modifier = Modifier, summaryPerCategory: SummaryPerCategory) {
|
fun CategoryCard(modifier: Modifier = Modifier, summaryPerCategory: SummaryPerCategory) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
@@ -250,6 +314,67 @@ fun CategoryCard(modifier: Modifier = Modifier, summaryPerCategory: SummaryPerCa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
@Composable
|
||||||
|
fun DayCard(modifier: Modifier = Modifier, summaryPerDay: SummaryPerDay) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxHeight(), verticalArrangement = Arrangement.Bottom,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "%.2f".format(summaryPerDay.amount),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontSize = (MaterialTheme.typography.labelSmall.fontSize.value - 2).sp,
|
||||||
|
)
|
||||||
|
val width = 45.dp
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(width)
|
||||||
|
.fillMaxHeight(0.2f + (0.98f - 0.2f) * summaryPerDay.percent)
|
||||||
|
.clip(RoundedCornerShape(width / 2))
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(width - 10.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
shape = RoundedCornerShape(width / 2)
|
||||||
|
)
|
||||||
|
.padding(vertical = 3.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
lineHeight = 10.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
text = summaryPerDay.day.format(DateTimeFormatter.ofPattern("dd"))
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
fontSize = (MaterialTheme.typography.labelSmall.fontSize.value - 2).sp,
|
||||||
|
lineHeight = 10.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
text = summaryPerDay.day.format(DateTimeFormatter.ofPattern("E"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@AllPreviews
|
@AllPreviews
|
||||||
@@ -259,6 +384,7 @@ fun PreviewStatisticScreen() {
|
|||||||
Scaffold {
|
Scaffold {
|
||||||
StatisticsScreen(
|
StatisticsScreen(
|
||||||
summaryPerCategoryList,
|
summaryPerCategoryList,
|
||||||
|
summaryPerDayList,
|
||||||
summaryAmount = 125.24,
|
summaryAmount = 125.24,
|
||||||
Currencies.entries.random(),
|
Currencies.entries.random(),
|
||||||
432.14
|
432.14
|
||||||
@@ -275,6 +401,7 @@ fun PreviewStatisticScreenWithNoData() {
|
|||||||
TripMoneyTheme {
|
TripMoneyTheme {
|
||||||
Scaffold {
|
Scaffold {
|
||||||
StatisticsScreen(
|
StatisticsScreen(
|
||||||
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
summaryAmount = 0.0,
|
summaryAmount = 0.0,
|
||||||
Currencies.entries.random(),
|
Currencies.entries.random(),
|
||||||
@@ -285,7 +412,6 @@ fun PreviewStatisticScreenWithNoData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val categories = listOf(
|
val categories = listOf(
|
||||||
Category(name = "Jedzenie", icon = Icons.RESTAURANT, color = colors.random()),
|
Category(name = "Jedzenie", icon = Icons.RESTAURANT, color = colors.random()),
|
||||||
Category(name = "Transport", icon = Icons.FLIGHT, color = colors.random()),
|
Category(name = "Transport", icon = Icons.FLIGHT, color = colors.random()),
|
||||||
@@ -304,3 +430,25 @@ val summaryPerCategoryList = listOf(
|
|||||||
SummaryPerCategory(categories[3], 50.0, 0.1f, Currencies.PLN),
|
SummaryPerCategory(categories[3], 50.0, 0.1f, Currencies.PLN),
|
||||||
SummaryPerCategory(categories[5], 50.0, 0.0001f, Currencies.PLN),
|
SummaryPerCategory(categories[5], 50.0, 0.0001f, Currencies.PLN),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
val summaryPerDayListRaw = listOf(
|
||||||
|
SummaryPerDay(LocalDate.now(), 50.0, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(1), 500.23, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(2), 1560.53, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(3), 700.32, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(4), 201.3, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(5), 2020.64, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(6), 510.43, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(7), 3050.12, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(8), 264.32, 0f),
|
||||||
|
SummaryPerDay(LocalDate.now().minusDays(9), 3596.64, 0f)
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
val highestAmount = summaryPerDayListRaw.maxOf { it.amount }
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
val summaryPerDayList = summaryPerDayListRaw.map {
|
||||||
|
it.copy(percent = ((it.amount / highestAmount)).toFloat())
|
||||||
|
}.sortedBy { it.day.toEpochDay() }
|
||||||
@@ -113,7 +113,7 @@ fun TripPickerScreen(
|
|||||||
}
|
}
|
||||||
}) { paddingValues ->
|
}) { paddingValues ->
|
||||||
if (trips.itemCount == 0) {
|
if (trips.itemCount == 0) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize().padding(10.dp), contentAlignment = Alignment.Center) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(string.no_trip_added),
|
text = stringResource(string.no_trip_added),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.paging.insertSeparators
|
|||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
import cc.n0th1ng.tripmoney.Filter
|
import cc.n0th1ng.tripmoney.Filter
|
||||||
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
|
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
|
||||||
|
import cc.n0th1ng.tripmoney.data.dto.SummaryPerDay
|
||||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||||
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
|
||||||
@@ -44,7 +45,11 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
return expenseRepo.getBudgetLeft(tripId)
|
return expenseRepo.getBudgetLeft(tripId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getExpensesDtoPaged(tripId: Int, search: String = "", filter: Filter = Filter()): Flow<PagingData<ExpenseDto>> =
|
fun getExpensesDtoPaged(
|
||||||
|
tripId: Int,
|
||||||
|
search: String = "",
|
||||||
|
filter: Filter = Filter()
|
||||||
|
): Flow<PagingData<ExpenseDto>> =
|
||||||
expenseRepo.getExpensesDtoPaged(tripId, search, filter).cachedIn(viewModelScope)
|
expenseRepo.getExpensesDtoPaged(tripId, search, filter).cachedIn(viewModelScope)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@@ -86,7 +91,11 @@ open class ExpenseAndCategoryViewModel @Inject constructor(
|
|||||||
}.cachedIn(viewModelScope)
|
}.cachedIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getExpensesDto(tripId: Int, search: String = "", filter: Filter = Filter()): Flow<List<ExpenseDto>> =
|
fun getExpensesDto(
|
||||||
|
tripId: Int,
|
||||||
|
search: String = "",
|
||||||
|
filter: Filter = Filter()
|
||||||
|
): Flow<List<ExpenseDto>> =
|
||||||
expenseRepo.getExpensesDto(tripId, search, filter)
|
expenseRepo.getExpensesDto(tripId, search, filter)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
@@ -184,6 +193,30 @@ 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)
|
||||||
|
|
||||||
|
return tripFlow.combine(expensesFlow) { trip, expenses ->
|
||||||
|
val summaryPerDayRaw = expenses.groupBy { it.expense.datetime.toLocalDate() }
|
||||||
|
.map { (day, expensesForDay) ->
|
||||||
|
val total = expensesForDay.sumOf { it.expense.convertedAmount() }
|
||||||
|
SummaryPerDay(
|
||||||
|
amount = total,
|
||||||
|
day = day,
|
||||||
|
percent = 0.0f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.sortedByDescending { it.day }
|
||||||
|
|
||||||
|
val highestAmount = summaryPerDayRaw.maxOf { it.amount }
|
||||||
|
summaryPerDayRaw.map {
|
||||||
|
it.copy(percent = ((it.amount / highestAmount)).toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
fun clearOldRates() {
|
fun clearOldRates() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|||||||
@@ -32,4 +32,17 @@
|
|||||||
<string name="export_csv_subttext">Zapisz wydatki z %s do pliku</string>
|
<string name="export_csv_subttext">Zapisz wydatki z %s do pliku</string>
|
||||||
<string name="add_new_category">Dodaj kategorie</string>
|
<string name="add_new_category">Dodaj kategorie</string>
|
||||||
<string name="edit_category">Edytuj kategorie</string>
|
<string name="edit_category">Edytuj kategorie</string>
|
||||||
|
<string name="archive">Archiwizuj</string>
|
||||||
|
<string name="you_want_archive">Chcesz zarchiwizować?</string>
|
||||||
|
<string name="archive_category_info">Żadne wydatki nie będą usunięte.</string>
|
||||||
|
<string name="delete_category_info">Wszystkie wydatki z kategorii %s zostaną usunięte.</string>
|
||||||
|
<string name="budget">Budżet</string>
|
||||||
|
<string name="money_left">Pozostałe środki</string>
|
||||||
|
<string name="add_expense_settings">Pokaż dodawanie wydatku na starcie</string>
|
||||||
|
<string name="yesterday">Wczoraj</string>
|
||||||
|
<string name="clear">Wyczyść</string>
|
||||||
|
<string name="no_expenses">Zacznij budżetowanie od dodania wydatków</string>
|
||||||
|
<string name="no_trip_picked">Wybierz wycieczkę żeby zobaczyć wydatki</string>
|
||||||
|
<string name="no_trip_added">Zacznij budżetowanie od dodania wycieczki</string>
|
||||||
|
<string name="no_expenses_summary">Brak wydatków do podsumowania</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<string name="archive">Archive</string>
|
<string name="archive">Archive</string>
|
||||||
<string name="you_want_archive">Do you want to archive?</string>
|
<string name="you_want_archive">Do you want to archive?</string>
|
||||||
<string name="archive_category_info">No expense will be deleted.</string>
|
<string name="archive_category_info">No expense will be deleted.</string>
|
||||||
<string name="delete_category_info">Your all expenses with category Hotel will be removed.</string>
|
<string name="delete_category_info">Your all expenses with category %s will be removed.</string>
|
||||||
<string name="budget">Budget</string>
|
<string name="budget">Budget</string>
|
||||||
<string name="money_left">Money left</string>
|
<string name="money_left">Money left</string>
|
||||||
<string name="add_expense_settings">Open add expense form on startup</string>
|
<string name="add_expense_settings">Open add expense form on startup</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user