diff --git a/core/ui/src/main/java/com/jacob/wakatimeapp/core/ui/components/cards/StatsChip.kt b/core/ui/src/main/java/com/jacob/wakatimeapp/core/ui/components/cards/StatsChip.kt index 72edbe2c..b4975eea 100644 --- a/core/ui/src/main/java/com/jacob/wakatimeapp/core/ui/components/cards/StatsChip.kt +++ b/core/ui/src/main/java/com/jacob/wakatimeapp/core/ui/components/cards/StatsChip.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.BaselineShift @@ -45,6 +46,7 @@ fun StatsChip( onClick: () -> Unit, roundedCornerPercent: Int, modifier: Modifier = Modifier, + statValueTextStyle: TextStyle = MaterialTheme.typography.displayLarge, ) { val gradientBrush = Brush.horizontalGradient(gradient.colorList) val shape = RoundedCornerShape(roundedCornerPercent) @@ -68,6 +70,7 @@ fun StatsChip( .size(size = 50.dp) .align(Alignment.BottomEnd), ) + // TODO: UPDATE TO USE SLOTS API Column( modifier = Modifier .padding( @@ -75,11 +78,9 @@ fun StatsChip( vertical = MaterialTheme.spacing.small, ), ) { - val streakValueTextStyle = MaterialTheme.typography.displayLarge - Text( text = buildAnnotatedString { - withStyle(style = streakValueTextStyle.toSpanStyle()) { + withStyle(style = statValueTextStyle.toSpanStyle()) { append(statsValue) } withStyle( @@ -91,7 +92,7 @@ fun StatsChip( } }, color = gradient.onStartColor, - modifier = Modifier.removeFontPadding(streakValueTextStyle), + modifier = Modifier.removeFontPadding(statValueTextStyle), ) Text( text = statsType, diff --git a/details/src/main/java/com/jacob/wakatimeapp/details/ui/DetailsPage.kt b/details/src/main/java/com/jacob/wakatimeapp/details/ui/DetailsPage.kt index 01836e2e..17cde318 100644 --- a/details/src/main/java/com/jacob/wakatimeapp/details/ui/DetailsPage.kt +++ b/details/src/main/java/com/jacob/wakatimeapp/details/ui/DetailsPage.kt @@ -115,9 +115,8 @@ private fun DetailsPageLoaded(viewState: DetailsPageViewState.Loaded, today: Loc state = pagerState, beyondBoundsPageCount = 1, ) { page -> - viewState.statsForProject when (pages[page]) { - Tabs.Time -> TimeTab(statsForProject = viewState.statsForProject, today) + Tabs.Time -> TimeTab(detailsPageData = viewState, today) Tabs.Languages -> LanguagesTab() Tabs.Editors -> EditorsTab() Tabs.OperatingSystems -> OperatingSystemsTab() diff --git a/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/ProjectHistory.kt b/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/ProjectHistory.kt index 953cf546..91e184cc 100644 --- a/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/ProjectHistory.kt +++ b/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/ProjectHistory.kt @@ -18,7 +18,7 @@ import com.jacob.wakatimeapp.core.ui.theme.WakaTimeAppTheme import com.jacob.wakatimeapp.core.ui.theme.cardHeader import com.jacob.wakatimeapp.core.ui.theme.cardSubtitle import com.jacob.wakatimeapp.core.ui.theme.spacing -import kotlinx.collections.immutable.ImmutableMap +import com.jacob.wakatimeapp.details.ui.DetailsPageViewState import kotlinx.datetime.LocalDate import kotlinx.datetime.format import kotlinx.datetime.format.DayOfWeekNames @@ -29,13 +29,13 @@ import java.util.Comparator.comparing private const val BaseYear = 2000 internal fun LazyListScope.projectHistory( - statsForProject: ImmutableMap, + detailsPageData: DetailsPageViewState.Loaded, ) { item { Text(text = "Project History", modifier = Modifier.padding(vertical = MaterialTheme.spacing.extraSmall)) } items( - items = statsForProject.filter { it.value != Time.ZERO } + items = detailsPageData.statsForProject.filter { it.value != Time.ZERO } .toSortedMap(comparing { -it.toEpochDays() }) .toList(), key = { it.first.toEpochDays() }, diff --git a/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/QuickStatsCards.kt b/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/QuickStatsCards.kt new file mode 100644 index 00000000..4721b182 --- /dev/null +++ b/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/QuickStatsCards.kt @@ -0,0 +1,139 @@ +package com.jacob.wakatimeapp.details.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.jacob.wakatimeapp.core.models.Time +import com.jacob.wakatimeapp.core.ui.WtaPreviews +import com.jacob.wakatimeapp.core.ui.components.cards.StatsChip +import com.jacob.wakatimeapp.core.ui.theme.WakaTimeAppTheme +import com.jacob.wakatimeapp.core.ui.theme.assets +import com.jacob.wakatimeapp.core.ui.theme.gradients +import com.jacob.wakatimeapp.core.ui.theme.spacing +import com.jacob.wakatimeapp.details.ui.DetailsPageViewState +import kotlinx.collections.immutable.toImmutableMap +import kotlinx.datetime.LocalDate +import kotlinx.datetime.format +import kotlinx.datetime.format.DayOfWeekNames +import kotlinx.datetime.format.MonthNames +import kotlinx.datetime.format.char + +@Composable +internal fun QuickStatsCards(detailsPageData: DetailsPageViewState.Loaded) { + val totalTime = remember(detailsPageData) { detailsPageData.statsForProject.values.fold(Time.ZERO, Time::plus) } + val averageTime = + remember(detailsPageData) { Time.fromDecimal(totalTime.decimal.div(detailsPageData.statsForProject.size)) } + val startDate = remember(detailsPageData) { detailsPageData.statsForProject.keys.minBy(LocalDate::toEpochDays) } + val numberOfDaysWorked = + remember(detailsPageData) { detailsPageData.statsForProject.values.filter { it != Time.ZERO }.size } + + Column( + Modifier.fillMaxWidth(), + Arrangement.spacedBy(MaterialTheme.spacing.sMedium), + ) { + StatsChip( + statsType = "Total Time on Project", + statsValue = totalTime.formattedPrint(), + statsValueSubText = "", + gradient = MaterialTheme.gradients.amin, + iconId = MaterialTheme.assets.icons.time, + onClick = {}, + roundedCornerPercent = 15, + ) + StatsChip( + statsType = "Average Time", + statsValue = averageTime.formattedPrint(), + statsValueSubText = "", + gradient = MaterialTheme.gradients.purpink, + iconId = MaterialTheme.assets.icons.time, + onClick = {}, + roundedCornerPercent = 15, + ) + Row(modifier = Modifier.fillMaxWidth()) { + val format = LocalDate.Format { + dayOfWeek(DayOfWeekNames.ENGLISH_ABBREVIATED) + chars(", ") + monthName(MonthNames.ENGLISH_ABBREVIATED) + char(' ') + dayOfMonth() + chars(", ") + yearTwoDigits(1960) + } + StatsChip( + statsType = "Start date", + statsValue = startDate.format(format), + statsValueSubText = "", + gradient = MaterialTheme.gradients.quepal, + iconId = MaterialTheme.assets.icons.time, + onClick = {}, + roundedCornerPercent = 15, + statValueTextStyle = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = Modifier.width(MaterialTheme.spacing.small)) + StatsChip( + statsType = "No. of days worked", + statsValue = numberOfDaysWorked.toString(), + statsValueSubText = "", + gradient = MaterialTheme.gradients.tealLove, + iconId = MaterialTheme.assets.icons.time, + onClick = {}, + roundedCornerPercent = 15, + statValueTextStyle = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + } + Row(modifier = Modifier.fillMaxWidth()) { + // expandable cards + // could replace tabs? + // when clicking on one of the cards will expand that card and shrink the other + // will expand in both X and Y, show pie chart after expanding + // before clicking/expanding + // 🟥 🟦 + // 🟧 🟪 + // after clicking/expanding + // 🟥🟥 + // 🟥🟥 + Text("Most used language") + Text("Most used editor") + Text("Most used os") + Text("Most used machine") + } + } +} + +@WtaPreviews +@Composable +private fun ProjectHistoryListPreview() { + WakaTimeAppTheme { + Surface(modifier = Modifier.fillMaxSize()) { + QuickStatsCards( + DetailsPageViewState.Loaded( + "", + mapOf( + LocalDate.fromEpochDays(1) to Time.fromDecimal(3f), + LocalDate.fromEpochDays(2) to Time.fromDecimal(1f), + LocalDate.fromEpochDays(3) to Time.fromDecimal(2f), + LocalDate.fromEpochDays(4) to Time.fromDecimal(2f), + LocalDate.fromEpochDays(5) to Time.fromDecimal(4f), + LocalDate.fromEpochDays(6) to Time.fromDecimal(3f), + LocalDate.fromEpochDays(7) to Time.fromDecimal(2f), + LocalDate.fromEpochDays(8) to Time.fromDecimal(3f), + LocalDate.fromEpochDays(9) to Time.fromDecimal(4f), + LocalDate.fromEpochDays(10) to Time.fromDecimal(1f), + ).toImmutableMap(), + ), + ) + } + } +} diff --git a/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/TimeTab.kt b/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/TimeTab.kt index e425aa0c..5b2d9c37 100644 --- a/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/TimeTab.kt +++ b/details/src/main/java/com/jacob/wakatimeapp/details/ui/components/TimeTab.kt @@ -8,17 +8,16 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.jacob.wakatimeapp.core.models.Time -import com.jacob.wakatimeapp.core.ui.WtaComponentPreviews +import com.jacob.wakatimeapp.core.ui.WtaPreviews import com.jacob.wakatimeapp.core.ui.components.VicoBarChart import com.jacob.wakatimeapp.core.ui.components.VicoBarChartData import com.jacob.wakatimeapp.core.ui.theme.WakaTimeAppTheme import com.jacob.wakatimeapp.core.ui.theme.spacing -import kotlinx.collections.immutable.ImmutableMap +import com.jacob.wakatimeapp.details.ui.DetailsPageViewState import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import kotlinx.datetime.Clock @@ -29,26 +28,21 @@ import kotlinx.datetime.toLocalDateTime private const val DaysInChart = 30 @Composable -internal fun TimeTab(statsForProject: ImmutableMap, today: LocalDate, modifier: Modifier = Modifier) { +internal fun TimeTab(detailsPageData: DetailsPageViewState.Loaded, today: LocalDate, modifier: Modifier = Modifier) { LazyColumn( verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small), modifier = modifier .fillMaxSize() .padding(horizontal = MaterialTheme.spacing.sMedium), ) { - item { RecentTimeSpentChart(statsForProject, today) } - item { QuickStatsCards() } - projectHistory(statsForProject) + item { RecentTimeSpentChart(detailsPageData, today) } + item { QuickStatsCards(detailsPageData) } + projectHistory(detailsPageData) } } @Composable -private fun QuickStatsCards() { - Text(text = "Quick Stats Cards") -} - -@Composable -private fun RecentTimeSpentChart(weeklyTimeSpent: ImmutableMap, today: LocalDate) = Surface( +private fun RecentTimeSpentChart(detailsPageData: DetailsPageViewState.Loaded, today: LocalDate) = Surface( modifier = Modifier .padding(horizontal = MaterialTheme.spacing.small) .padding(top = MaterialTheme.spacing.sMedium) @@ -58,7 +52,7 @@ private fun RecentTimeSpentChart(weeklyTimeSpent: ImmutableMap, tonalElevation = 2.dp, ) { VicoBarChart( - timeData = weeklyTimeSpent.values.toMutableList().takeLast(DaysInChart).toImmutableList(), + timeData = detailsPageData.statsForProject.values.toMutableList().takeLast(DaysInChart).toImmutableList(), xAxisFormatter = VicoBarChartData.getDefaultXAxisFormatter(today, skipCount = 5), modifier = Modifier.padding(MaterialTheme.spacing.small), columnWidth = 30f, @@ -66,24 +60,27 @@ private fun RecentTimeSpentChart(weeklyTimeSpent: ImmutableMap, ) } -@WtaComponentPreviews +@WtaPreviews @Composable private fun ProjectHistoryListPreview() { WakaTimeAppTheme { Surface { TimeTab( - mapOf( - LocalDate.fromEpochDays(1) to Time.fromDecimal(3f), - LocalDate.fromEpochDays(2) to Time.fromDecimal(1f), - LocalDate.fromEpochDays(3) to Time.fromDecimal(2f), - LocalDate.fromEpochDays(4) to Time.fromDecimal(2f), - LocalDate.fromEpochDays(5) to Time.fromDecimal(4f), - LocalDate.fromEpochDays(6) to Time.fromDecimal(3f), - LocalDate.fromEpochDays(7) to Time.fromDecimal(2f), - LocalDate.fromEpochDays(8) to Time.fromDecimal(3f), - LocalDate.fromEpochDays(9) to Time.fromDecimal(4f), - LocalDate.fromEpochDays(10) to Time.fromDecimal(1f), - ).toImmutableMap(), + DetailsPageViewState.Loaded( + "", + mapOf( + LocalDate.fromEpochDays(1) to Time.fromDecimal(3f), + LocalDate.fromEpochDays(2) to Time.fromDecimal(1f), + LocalDate.fromEpochDays(3) to Time.fromDecimal(2f), + LocalDate.fromEpochDays(4) to Time.fromDecimal(2f), + LocalDate.fromEpochDays(5) to Time.fromDecimal(4f), + LocalDate.fromEpochDays(6) to Time.fromDecimal(3f), + LocalDate.fromEpochDays(7) to Time.fromDecimal(2f), + LocalDate.fromEpochDays(8) to Time.fromDecimal(3f), + LocalDate.fromEpochDays(9) to Time.fromDecimal(4f), + LocalDate.fromEpochDays(10) to Time.fromDecimal(1f), + ).toImmutableMap(), + ), today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, ) }