|
|
|
|
@@ -4,15 +4,19 @@ import android.annotation.SuppressLint
|
|
|
|
|
import android.os.Build
|
|
|
|
|
import androidx.annotation.RequiresApi
|
|
|
|
|
import androidx.compose.foundation.background
|
|
|
|
|
import androidx.compose.foundation.horizontalScroll
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxHeight
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
|
import androidx.compose.foundation.layout.heightIn
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
|
|
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.style.TextAlign
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import androidx.compose.ui.unit.sp
|
|
|
|
|
import androidx.core.graphics.toColorInt
|
|
|
|
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|
|
|
|
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
|
|
|
|
|
import cc.n0th1ng.tripmoney.data.dto.SummaryPerDay
|
|
|
|
|
import cc.n0th1ng.tripmoney.data.entity.Category
|
|
|
|
|
import cc.n0th1ng.tripmoney.data.entity.Trip
|
|
|
|
|
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.TripViewModel
|
|
|
|
|
import com.composables.icons.materialsymbols.outlined.R
|
|
|
|
|
import java.time.LocalDate
|
|
|
|
|
import java.time.format.DateTimeFormatter
|
|
|
|
|
|
|
|
|
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
|
|
|
@Composable
|
|
|
|
|
@@ -60,11 +68,14 @@ fun StatisticsScreen() {
|
|
|
|
|
val currentTrip by tripViewModel.getTrip(currentTripId).collectAsState(Trip.DUMMY)
|
|
|
|
|
val summaryPerCategoryList by expenseAndCategoryViewModel.getSummaryPerCategory(currentTripId)
|
|
|
|
|
.collectAsState(emptyList())
|
|
|
|
|
val summaryPerDayList by expenseAndCategoryViewModel.getSummaryPerDay(currentTripId)
|
|
|
|
|
.collectAsState(emptyList())
|
|
|
|
|
val summaryAmount by expenseAndCategoryViewModel.getSummaryAmount(currentTripId)
|
|
|
|
|
.collectAsState(0.0)
|
|
|
|
|
val moneyLeft by expenseAndCategoryViewModel.getBudgetLeft(currentTripId).collectAsState(null)
|
|
|
|
|
StatisticsScreen(
|
|
|
|
|
summaryPerCategoryList,
|
|
|
|
|
summaryPerDayList,
|
|
|
|
|
summaryAmount,
|
|
|
|
|
Currencies.valueOf(currentTrip?.currency ?: Currencies.default().name),
|
|
|
|
|
moneyLeft
|
|
|
|
|
@@ -75,6 +86,7 @@ fun StatisticsScreen() {
|
|
|
|
|
@Composable
|
|
|
|
|
fun StatisticsScreen(
|
|
|
|
|
summaryPerCategoryList: List<SummaryPerCategory>,
|
|
|
|
|
summaryPerDayList: List<SummaryPerDay>,
|
|
|
|
|
summaryAmount: Double,
|
|
|
|
|
tripCurrency: Currencies,
|
|
|
|
|
moneyLeft: Double?
|
|
|
|
|
@@ -101,8 +113,11 @@ fun StatisticsScreen(
|
|
|
|
|
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
|
|
|
|
|
fun SummaryPerCategoryCard(summaryPerCategoryList: List<SummaryPerCategory>) {
|
|
|
|
|
fun SummaryPerCategoryCard(
|
|
|
|
|
summaryPerCategoryList: List<SummaryPerCategory>,
|
|
|
|
|
modifier: Modifier = Modifier
|
|
|
|
|
) {
|
|
|
|
|
ElevatedCard(
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
modifier = modifier.fillMaxWidth(),
|
|
|
|
|
colors = CardDefaults.elevatedCardColors()
|
|
|
|
|
.copy(containerColor = MaterialTheme.colorScheme.surfaceContainer)
|
|
|
|
|
) {
|
|
|
|
|
if (summaryPerCategoryList.isEmpty()) {
|
|
|
|
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
|
|
|
Box(
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.fillMaxSize()
|
|
|
|
|
.padding(10.dp),
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
) {
|
|
|
|
|
Text(
|
|
|
|
|
text = stringResource(cc.n0th1ng.tripmoney.R.string.no_expenses_summary),
|
|
|
|
|
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
|
|
|
|
|
fun CategoryCard(modifier: Modifier = Modifier, summaryPerCategory: SummaryPerCategory) {
|
|
|
|
|
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")
|
|
|
|
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
|
|
|
@AllPreviews
|
|
|
|
|
@@ -259,6 +384,7 @@ fun PreviewStatisticScreen() {
|
|
|
|
|
Scaffold {
|
|
|
|
|
StatisticsScreen(
|
|
|
|
|
summaryPerCategoryList,
|
|
|
|
|
summaryPerDayList,
|
|
|
|
|
summaryAmount = 125.24,
|
|
|
|
|
Currencies.entries.random(),
|
|
|
|
|
432.14
|
|
|
|
|
@@ -275,6 +401,7 @@ fun PreviewStatisticScreenWithNoData() {
|
|
|
|
|
TripMoneyTheme {
|
|
|
|
|
Scaffold {
|
|
|
|
|
StatisticsScreen(
|
|
|
|
|
emptyList(),
|
|
|
|
|
emptyList(),
|
|
|
|
|
summaryAmount = 0.0,
|
|
|
|
|
Currencies.entries.random(),
|
|
|
|
|
@@ -285,7 +412,6 @@ fun PreviewStatisticScreenWithNoData() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val categories = listOf(
|
|
|
|
|
Category(name = "Jedzenie", icon = Icons.RESTAURANT, color = colors.random()),
|
|
|
|
|
Category(name = "Transport", icon = Icons.FLIGHT, color = colors.random()),
|
|
|
|
|
@@ -303,4 +429,26 @@ val summaryPerCategoryList = listOf(
|
|
|
|
|
SummaryPerCategory(categories[2], 80.0, 0.2f, Currencies.PLN),
|
|
|
|
|
SummaryPerCategory(categories[3], 50.0, 0.1f, 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() }
|