Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButton
Expand Down Expand Up @@ -87,6 +89,8 @@ data class HistoryViewState(
val snackbarHostState = remember { SnackbarHostState() }
val isFABVisible = rememberSaveable { mutableStateOf(true) }
val timeZone = remember { TimeZone.currentSystemDefault() }
var showDatePicker by remember { mutableStateOf(false) }
var calendarMode by rememberSaveable { mutableStateOf(false) }

val nestedScrollConnection = remember {
object : NestedScrollConnection {
Expand All @@ -102,7 +106,7 @@ data class HistoryViewState(
snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = {
AnimatedVisibility(
visible = isFABVisible.value && !displayLoading,
visible = isFABVisible.value && !displayLoading && !calendarMode,
enter = slideInVertically(initialOffsetY = { it * 2 }),
exit = slideOutVertically(targetOffsetY = { it * 2 }),
) {
Expand Down Expand Up @@ -150,9 +154,6 @@ data class HistoryViewState(
)
}
) { contentPadding ->
var showDatePicker by remember { mutableStateOf(false) }
var calendarMode by rememberSaveable { mutableStateOf(false) }

if (showDatePicker) {
DatePickerDialog(
initialDate = selectedDate,
Expand All @@ -174,57 +175,82 @@ data class HistoryViewState(
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
IconButton(onClick = {
intent(HistoryIntent.FetchSmokes(selectedDate.minusDays(1, timeZone)))
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}

Text(
modifier = Modifier.clickable { showDatePicker = true },
text = selectedDate.toLocalDateTime(timeZone).dateFormattedUi(),
)

IconButton(onClick = {
intent(HistoryIntent.FetchSmokes(selectedDate.plusDays(1, timeZone)))
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null
)
}
}

Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip(
modifier = Modifier.weight(1f),
selected = !calendarMode,
onClick = { calendarMode = false },
leadingIcon = {
Icon(
imageVector = Icons.AutoMirrored.Filled.ViewList,
contentDescription = null
)
},
label = { Text("List") },
)
FilterChip(
modifier = Modifier.weight(1f),
selected = calendarMode,
onClick = { calendarMode = true },
leadingIcon = {
Icon(
imageVector = Icons.Filled.CalendarMonth,
contentDescription = null
)
},
label = { Text("Calendar") },
)
}

Stat(
titleResourceId = R.string.history_smoked,
count = smokes?.size ?: 0,
isLoading = displayLoading
)
if (!calendarMode) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(onClick = {
intent(HistoryIntent.FetchSmokes(selectedDate.minusDays(1, timeZone)))
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}

Text(
modifier = Modifier.clickable { showDatePicker = true },
text = selectedDate.toLocalDateTime(timeZone).dateFormattedUi(),
)

IconButton(onClick = {
intent(HistoryIntent.FetchSmokes(selectedDate.plusDays(1, timeZone)))
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null
)
}
}

Stat(
titleResourceId = R.string.history_smoked,
count = smokes?.size ?: 0,
isLoading = displayLoading
)
}

if (calendarMode && !displayLoading) {
CalendarMonthCard(
selectedLocalDate = selectedDate.toLocalDateTime(timeZone).date,
monthCounts = monthCounts,
onShiftMonth = { amount ->
val current = selectedDate.toLocalDateTime(timeZone).date
val shifted = current.plus(DatePeriod(months = amount))
intent(HistoryIntent.FetchSmokes(LocalDate(shifted.year, shifted.monthNumber, 1).atStartOfDayIn(timeZone)))
},
onPickDay = { picked ->
calendarMode = false
intent(HistoryIntent.FetchSmokes(picked.atStartOfDayIn(timeZone)))
}
)
Expand All @@ -248,7 +274,7 @@ data class HistoryViewState(
)
}
}
} else if (!smokes.isNullOrEmpty()) {
} else if (!calendarMode && !smokes.isNullOrEmpty()) {
LazyColumn(
modifier = Modifier
.padding(top = 16.dp)
Expand All @@ -269,7 +295,7 @@ data class HistoryViewState(
HorizontalDivider()
}
}
} else {
} else if (!calendarMode) {
EmptySmokes()
}
}
Expand All @@ -294,6 +320,7 @@ private fun kotlinx.datetime.LocalDateTime.dateFormattedUi(): String {
private fun CalendarMonthCard(
selectedLocalDate: LocalDate,
monthCounts: Map<Int, Int>,
onShiftMonth: (Int) -> Unit,
onPickDay: (LocalDate) -> Unit,
) {
val monthStart = LocalDate(selectedLocalDate.year, selectedLocalDate.monthNumber, 1)
Expand All @@ -309,13 +336,38 @@ private fun CalendarMonthCard(
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = selectedLocalDate.toUiMonthYear(),
style = MaterialTheme.typography.titleSmall,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(onClick = { onShiftMonth(-1) }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = selectedLocalDate.toUiMonthYear(),
style = MaterialTheme.typography.titleSmall,
)
Text(
text = "${monthCounts.values.sum()} smokes this month",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
IconButton(onClick = { onShiftMonth(1) }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
)
}
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
listOf("M", "T", "W", "T", "F", "S", "S").forEach { label ->
Box(modifier = Modifier.width(42.dp), contentAlignment = Alignment.Center) {
Box(modifier = Modifier.width(48.dp), contentAlignment = Alignment.Center) {
Text(text = label, style = MaterialTheme.typography.labelSmall)
}
}
Expand All @@ -332,7 +384,7 @@ private fun CalendarMonthCard(
val slot = row * 7 + column
val day = slot - leadingEmptySlots + 1
if (day !in 1..daysInMonth) {
Spacer(modifier = Modifier.width(42.dp))
Spacer(modifier = Modifier.width(48.dp))
} else {
val count = monthCounts[day] ?: 0
val date = LocalDate(selectedLocalDate.year, selectedLocalDate.monthNumber, day)
Expand All @@ -345,8 +397,8 @@ private fun CalendarMonthCard(
}
Box(
modifier = Modifier
.width(42.dp)
.height(54.dp)
.width(48.dp)
.height(64.dp)
.clip(RoundedCornerShape(12.dp))
.background(background)
.clickable { onPickDay(date) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
Expand Down Expand Up @@ -327,8 +326,6 @@ private fun GreetingSection(
isLoading: Boolean,
) {
if (greetingTitle == null && financialSummary == null && gamificationSummary == null) return
var showStreakInfo by remember { mutableStateOf(false) }

Box(
modifier = Modifier
.fillMaxWidth()
Expand All @@ -349,31 +346,10 @@ private fun GreetingSection(
)
}
gamificationSummary?.let {
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Points ${it.points} · Streak ${it.currentStreakHours}h",
style = MaterialTheme.typography.bodySmall,
)
IconButton(
onClick = { showStreakInfo = !showStreakInfo },
modifier = Modifier.size(18.dp),
) {
Text(
text = "?",
style = MaterialTheme.typography.labelSmall,
)
}
}
if (showStreakInfo) {
Text(
text = "Streak is the time since the last logged cigarette in the current cycle.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Text(
text = "Points ${it.points}",
style = MaterialTheme.typography.bodySmall,
)
}
if (canStartNewDay) {
androidx.compose.material3.TextButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class SettingsViewModel @Inject constructor(
SettingsResult.Loading -> previous.copy(displayLoading = true, infoMessage = null)
SettingsResult.PreferencesSaved -> previous.copy(
displayLoading = false,
infoMessage = "Saved",
infoMessage = null,
)
is SettingsResult.Error -> previous.copy(
displayLoading = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ data class SettingsViewState(
)

AccountTierCard(tier = preferences.accountTier)
ProgressCard()

Text(
text = "About",
Expand Down Expand Up @@ -480,6 +481,23 @@ private fun AccountTierCard(tier: AccountTier) {
}
}

@Composable
private fun ProgressCard() {
SettingsCard(title = "Points") {
Text(
text = "Points are earned from smoke-free gaps. Longer gaps increase progress, and the score is meant to reflect pace rather than perfection.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Streak details stay out of Home for now until the progress model is clearer and easier to trust.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

@Composable
private fun SettingsCard(
title: String,
Expand Down
Loading
Loading