From 7744d56d11150bfb26967e2be9ed365ef5deb73e Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Wed, 21 Jan 2026 08:46:04 +1100 Subject: [PATCH 1/9] Change stage indicator to rhombus --- .../fasttrack/screens/fasting/TimeLine.kt | 280 +++++++++--------- 1 file changed, 146 insertions(+), 134 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt index 56e0a39e..a4c197f9 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt @@ -9,7 +9,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import com.darkrockstudios.apps.fasttrack.data.Phase @@ -19,11 +22,11 @@ import kotlin.math.min import kotlin.time.Duration.Companion.hours val gaugeColors = listOf( - Color.White, - Color.Green, - Color.Yellow, - Color.Red, - Color.Magenta + Color.White, + Color.Green, + Color.Yellow, + Color.Red, + Color.Magenta ) /** @@ -31,133 +34,142 @@ val gaugeColors = listOf( */ @Composable fun TimeLine( - elapsedHours: Double, - modifier: Modifier = Modifier, - onPhaseClick: (Phase) -> Unit = {} + elapsedHours: Double, + modifier: Modifier = Modifier, + onPhaseClick: (Phase) -> Unit = {} ) { - val padding = 16.dp - val spacing = 18.dp - val barSize = 16.dp - val needleSize = 3.dp - val needleRadius = 4.dp - - val outlineColor = MaterialTheme.colorScheme.onBackground - - Canvas( - modifier = modifier - .fillMaxWidth() - .height(padding + barSize) - .pointerInput(Stages.phases) { - detectTapGestures { offset -> - val paddingPx = padding.toPx() - val spacingPx = spacing.toPx() - val barSizePx = barSize.toPx() - val availableWidth = size.width - paddingPx - val phaseWidth = (availableWidth / Stages.phases.size) - spacingPx - val startY = paddingPx - val yOk = abs(offset.y - startY) <= (barSizePx / 2f) - if (yOk) { - Stages.phases.forEachIndexed { index, phase -> - val startX = (index * phaseWidth) + (index * spacingPx) + paddingPx - val endX = startX + phaseWidth - if (offset.x in startX..endX) { - onPhaseClick(phase) - return@detectTapGestures - } - } - } - } - } - ) { - val lastPhase = Stages.phases.last() - val lastPhaseHoursWeighted = lastPhase.hours * 1.5f - - val availableWidth = size.width - padding.toPx() - val phaseWidth = (availableWidth / Stages.phases.size) - spacing.toPx() - - val curPhase = Stages.getCurrentPhase(elapsedHours.hours) - - // Draw the bubbles (phases) - Stages.phases.forEachIndexed { index, phase -> - val startX = (index * phaseWidth) + (index * spacing.toPx()) + padding.toPx() - val startY = padding.toPx() - - // Current phase, thicket orange outline - if (curPhase == phase) { - // Outline - drawLine( - color = Color(0xFFE67E22), - start = Offset(startX, startY), - end = Offset(startX + phaseWidth, startY), - strokeWidth = barSize.toPx(), - cap = StrokeCap.Round - ) - - // Current phase - filled - drawLine( - color = gaugeColors[index], - start = Offset(startX, startY), - end = Offset(startX + phaseWidth, startY), - strokeWidth = barSize.toPx() * 0.7f, - cap = StrokeCap.Round - ) - } else { - // Other phases - thinner "onBackground" outline - - // Thinner outline - drawLine( - color = outlineColor, - start = Offset(startX, startY), - end = Offset(startX + phaseWidth, startY), - strokeWidth = barSize.toPx(), - cap = StrokeCap.Round - ) - - // Phase color - drawLine( - color = gaugeColors[index], - start = Offset(startX, startY), - end = Offset(startX + phaseWidth, startY), - strokeWidth = barSize.toPx() * 0.8f, // Slightly thinner - cap = StrokeCap.Round - ) - } - } - - // Draw the needle - if (elapsedHours > 0) { - val curPhaseIndex = Stages.phases.indexOf(curPhase) - val nextPhaseHours: Float = if (curPhaseIndex + 1 < Stages.phases.size) { - val nextPhase = Stages.phases[curPhaseIndex + 1] - nextPhase.hours.toFloat() - } else { - lastPhaseHoursWeighted - } - val phaseLength = (nextPhaseHours - curPhase.hours) - val timeIntoPhase = elapsedHours - curPhase.hours - - val percent = min(timeIntoPhase / phaseLength, 1.0) - - val halfPadding = padding.toPx() / 2f - - val startX = (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) + padding.toPx() - val x = (startX + (phaseWidth * percent)).toFloat() - - // Draw needle line - drawLine( - color = Color.DarkGray, - start = Offset(x, halfPadding), - end = Offset(x, barSize.toPx() + halfPadding), - strokeWidth = needleSize.toPx(), - cap = StrokeCap.Square - ) - - // Draw needle circle - drawCircle( - color = Color.DarkGray, - radius = needleRadius.toPx(), - center = Offset(x, barSize.toPx() + halfPadding + needleRadius.toPx()) - ) - } - } -} + val padding = 16.dp + val spacing = 4.dp // Reduced spacing since rhombuses connect + val barSize = 16.dp + val needleSize = 3.dp + val needleRadius = 4.dp + val slantOffset = 8.dp // How much the rhombus leans to the right + + val outlineColor = MaterialTheme.colorScheme.onBackground + + Canvas( + modifier = modifier + .fillMaxWidth() + .height(padding + barSize) + .pointerInput(Stages.phases) { + detectTapGestures { offset -> + val paddingPx = padding.toPx() + val spacingPx = spacing.toPx() + val barSizePx = barSize.toPx() + val availableWidth = size.width - paddingPx + val phaseWidth = (availableWidth / Stages.phases.size) - spacingPx + val startY = paddingPx + val yOk = abs(offset.y - startY) <= (barSizePx / 2f) + if (yOk) { + Stages.phases.forEachIndexed { index, phase -> + val startX = (index * phaseWidth) + (index * spacingPx) + paddingPx + val endX = startX + phaseWidth + if (offset.x in startX..endX) { + onPhaseClick(phase) + return@detectTapGestures + } + } + } + } + } + ) { + val lastPhase = Stages.phases.last() + val lastPhaseHoursWeighted = lastPhase.hours * 1.5f + + val availableWidth = size.width - padding.toPx() + val phaseWidth = (availableWidth / Stages.phases.size) - spacing.toPx() + + val curPhase = Stages.getCurrentPhase(elapsedHours.hours) + + val slantOffsetPx = slantOffset.toPx() + val barSizePx = barSize.toPx() + + // Draw the rhombuses (phases) + Stages.phases.forEachIndexed { index, phase -> + val startX = (index * phaseWidth) + (index * spacing.toPx()) + padding.toPx() + val startY = padding.toPx() + + // Create rhombus path (parallelogram leaning right) + val rhombusPath = Path().apply { + // Top left + moveTo(startX + slantOffsetPx, startY - barSizePx / 2) + // Top right + lineTo(startX + phaseWidth + slantOffsetPx, startY - barSizePx / 2) + // Bottom right + lineTo(startX + phaseWidth, startY + barSizePx / 2) + // Bottom left + lineTo(startX, startY + barSizePx / 2) + close() + } + + // Current phase, thicker orange outline + if (curPhase == phase) { + // Outline + drawPath( + path = rhombusPath, + color = Color(0xFFE67E22), + style = Stroke(width = 3.dp.toPx()) + ) + + // Current phase - filled + drawPath( + path = rhombusPath, + color = gaugeColors[index], + style = Fill + ) + } else { + // Other phases - thinner "onBackground" outline + + // Outline + drawPath( + path = rhombusPath, + color = outlineColor, + style = Stroke(width = 2.dp.toPx()) + ) + + // Phase color + drawPath( + path = rhombusPath, + color = gaugeColors[index], + style = Fill + ) + } + } + + // Draw the needle + if (elapsedHours > 0) { + val curPhaseIndex = Stages.phases.indexOf(curPhase) + val nextPhaseHours: Float = if (curPhaseIndex + 1 < Stages.phases.size) { + val nextPhase = Stages.phases[curPhaseIndex + 1] + nextPhase.hours.toFloat() + } else { + lastPhaseHoursWeighted + } + val phaseLength = (nextPhaseHours - curPhase.hours) + val timeIntoPhase = elapsedHours - curPhase.hours + + val percent = min(timeIntoPhase / phaseLength, 1.0) + + val halfPadding = padding.toPx() / 2f + + val startX = (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) + padding.toPx() + val x = (startX + (phaseWidth * percent)).toFloat() + + // Draw needle line + drawLine( + color = Color.DarkGray, + start = Offset(x, halfPadding), + end = Offset(x, barSize.toPx() + halfPadding), + strokeWidth = needleSize.toPx(), + cap = StrokeCap.Square + ) + + // Draw needle circle + drawCircle( + color = Color.DarkGray, + radius = needleRadius.toPx(), + center = Offset(x, barSize.toPx() + halfPadding + needleRadius.toPx()) + ) + } + } +} \ No newline at end of file From 0f4559517d03bed343205710941b7bedfe587ce6 Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Wed, 21 Jan 2026 08:54:33 +1100 Subject: [PATCH 2/9] Improve centreing of stages --- .../fasttrack/screens/fasting/TimeLine.kt | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt index a4c197f9..0ba4b75f 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt @@ -56,14 +56,19 @@ fun TimeLine( val paddingPx = padding.toPx() val spacingPx = spacing.toPx() val barSizePx = barSize.toPx() - val availableWidth = size.width - paddingPx - val phaseWidth = (availableWidth / Stages.phases.size) - spacingPx + val slantOffsetPx = slantOffset.toPx() + + // Calculate total width needed for all rhombuses + val phaseWidth = (size.width - (2 * paddingPx) - (Stages.phases.size - 1) * spacingPx) / Stages.phases.size + val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacingPx) + slantOffsetPx + val startOffset = (size.width - totalWidth) / 2f + val startY = paddingPx val yOk = abs(offset.y - startY) <= (barSizePx / 2f) if (yOk) { Stages.phases.forEachIndexed { index, phase -> - val startX = (index * phaseWidth) + (index * spacingPx) + paddingPx - val endX = startX + phaseWidth + val startX = startOffset + (index * phaseWidth) + (index * spacingPx) + val endX = startX + phaseWidth + slantOffsetPx if (offset.x in startX..endX) { onPhaseClick(phase) return@detectTapGestures @@ -76,17 +81,21 @@ fun TimeLine( val lastPhase = Stages.phases.last() val lastPhaseHoursWeighted = lastPhase.hours * 1.5f - val availableWidth = size.width - padding.toPx() - val phaseWidth = (availableWidth / Stages.phases.size) - spacing.toPx() - - val curPhase = Stages.getCurrentPhase(elapsedHours.hours) - val slantOffsetPx = slantOffset.toPx() val barSizePx = barSize.toPx() + + // Calculate total width needed for all rhombuses + val phaseWidth = (size.width - (2 * padding.toPx()) - (Stages.phases.size - 1) * spacing.toPx()) / Stages.phases.size + val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacing.toPx()) + slantOffsetPx + + // Center the rhombuses + val startOffset = (size.width - totalWidth) / 2f + + val curPhase = Stages.getCurrentPhase(elapsedHours.hours) // Draw the rhombuses (phases) Stages.phases.forEachIndexed { index, phase -> - val startX = (index * phaseWidth) + (index * spacing.toPx()) + padding.toPx() + val startX = startOffset + (index * phaseWidth) + (index * spacing.toPx()) val startY = padding.toPx() // Create rhombus path (parallelogram leaning right) @@ -104,35 +113,35 @@ fun TimeLine( // Current phase, thicker orange outline if (curPhase == phase) { - // Outline + // Draw filled shape first + drawPath( + path = rhombusPath, + color = gaugeColors[index], + style = Fill + ) + + // Draw orange outline on top drawPath( path = rhombusPath, color = Color(0xFFE67E22), style = Stroke(width = 3.dp.toPx()) ) - - // Current phase - filled + } else { + // Other phases - draw fill first, then outline + + // Phase color fill drawPath( path = rhombusPath, color = gaugeColors[index], style = Fill ) - } else { - // Other phases - thinner "onBackground" outline - - // Outline + + // Outline on top drawPath( path = rhombusPath, color = outlineColor, style = Stroke(width = 2.dp.toPx()) ) - - // Phase color - drawPath( - path = rhombusPath, - color = gaugeColors[index], - style = Fill - ) } } @@ -152,7 +161,7 @@ fun TimeLine( val halfPadding = padding.toPx() / 2f - val startX = (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) + padding.toPx() + val startX = startOffset + (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) val x = (startX + (phaseWidth * percent)).toFloat() // Draw needle line From 7a403c5b72859e419c6302a22b28d3014dc6aebf Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:07:00 +1100 Subject: [PATCH 3/9] Add blink animation to current stage --- .../fasttrack/screens/fasting/TimeLine.kt | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt index 0ba4b75f..5f427b2b 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt @@ -1,11 +1,17 @@ package com.darkrockstudios.apps.fasttrack.screens.fasting +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color @@ -13,6 +19,7 @@ import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.lerp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import com.darkrockstudios.apps.fasttrack.data.Phase @@ -39,26 +46,39 @@ fun TimeLine( onPhaseClick: (Phase) -> Unit = {} ) { val padding = 16.dp - val spacing = 4.dp // Reduced spacing since rhombuses connect + val spacing = 4.dp val barSize = 16.dp val needleSize = 3.dp val needleRadius = 4.dp - val slantOffset = 8.dp // How much the rhombus leans to the right + val slantOffset = 8.dp val outlineColor = MaterialTheme.colorScheme.onBackground + + val curPhase = Stages.getCurrentPhase(elapsedHours.hours) + + // Continuous blink animation for current phase + val infiniteTransition = rememberInfiniteTransition(label = "phase_blink") + val blinkProgress by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 500), + repeatMode = RepeatMode.Reverse + ), + label = "blink_progress" + ) Canvas( modifier = modifier .fillMaxWidth() .height(padding + barSize) - .pointerInput(Stages.phases) { + .pointerInput(curPhase) { detectTapGestures { offset -> val paddingPx = padding.toPx() val spacingPx = spacing.toPx() val barSizePx = barSize.toPx() val slantOffsetPx = slantOffset.toPx() - // Calculate total width needed for all rhombuses val phaseWidth = (size.width - (2 * paddingPx) - (Stages.phases.size - 1) * spacingPx) / Stages.phases.size val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacingPx) + slantOffsetPx val startOffset = (size.width - totalWidth) / 2f @@ -84,59 +104,47 @@ fun TimeLine( val slantOffsetPx = slantOffset.toPx() val barSizePx = barSize.toPx() - // Calculate total width needed for all rhombuses val phaseWidth = (size.width - (2 * padding.toPx()) - (Stages.phases.size - 1) * spacing.toPx()) / Stages.phases.size val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacing.toPx()) + slantOffsetPx - // Center the rhombuses val startOffset = (size.width - totalWidth) / 2f - val curPhase = Stages.getCurrentPhase(elapsedHours.hours) - - // Draw the rhombuses (phases) Stages.phases.forEachIndexed { index, phase -> val startX = startOffset + (index * phaseWidth) + (index * spacing.toPx()) val startY = padding.toPx() - // Create rhombus path (parallelogram leaning right) val rhombusPath = Path().apply { - // Top left moveTo(startX + slantOffsetPx, startY - barSizePx / 2) - // Top right lineTo(startX + phaseWidth + slantOffsetPx, startY - barSizePx / 2) - // Bottom right lineTo(startX + phaseWidth, startY + barSizePx / 2) - // Bottom left lineTo(startX, startY + barSizePx / 2) close() } - // Current phase, thicker orange outline if (curPhase == phase) { - // Draw filled shape first drawPath( path = rhombusPath, color = gaugeColors[index], style = Fill ) - // Draw orange outline on top + // Continuously animate between orange and yellow + val baseOutlineColor = Color(0xFFE67E22) // Orange + val blinkColor = Color.Yellow + val currentOutlineColor = lerp(baseOutlineColor, blinkColor, blinkProgress) + drawPath( path = rhombusPath, - color = Color(0xFFE67E22), + color = currentOutlineColor, style = Stroke(width = 3.dp.toPx()) ) } else { - // Other phases - draw fill first, then outline - - // Phase color fill drawPath( path = rhombusPath, color = gaugeColors[index], style = Fill ) - // Outline on top drawPath( path = rhombusPath, color = outlineColor, @@ -164,7 +172,6 @@ fun TimeLine( val startX = startOffset + (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) val x = (startX + (phaseWidth * percent)).toFloat() - // Draw needle line drawLine( color = Color.DarkGray, start = Offset(x, halfPadding), @@ -173,7 +180,6 @@ fun TimeLine( cap = StrokeCap.Square ) - // Draw needle circle drawCircle( color = Color.DarkGray, radius = needleRadius.toPx(), From edd0c1144271aca63a6ce7e095c2b4ce6f475aa7 Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:28:39 +1100 Subject: [PATCH 4/9] After 24h add days below the current timer --- .../screens/fasting/FastingScreen.kt | 42 ++++++++++++------- .../screens/fasting/FastingViewModel.kt | 16 ++++++- .../screens/fasting/IFastingViewModel.kt | 1 + 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt index 80981953..03d67542 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt @@ -298,22 +298,36 @@ private fun FastHeadingContent( Spacer(modifier = Modifier.size(height = spacing.large, width = 1.dp)) // Timer - Row( - verticalAlignment = Alignment.Bottom, + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(bottom = spacing.large) ) { - Text( - text = uiState.timerText, - style = typography.timerText(), - color = MaterialTheme.colorScheme.onBackground, - fontWeight = FontWeight.Bold, - ) - Text( - text = uiState.milliseconds, - style = typography.timerMilliseconds(), - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.padding(start = spacing.small, bottom = spacing.small) - ) + Row( + verticalAlignment = Alignment.Bottom + ) { + Text( + text = uiState.timerText, + style = typography.timerText(), + color = MaterialTheme.colorScheme.onBackground, + fontWeight = FontWeight.Bold, + ) + Text( + text = uiState.milliseconds, + style = typography.timerMilliseconds(), + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.padding(start = spacing.small, bottom = spacing.small) + ) + } + + // Days and hours text (shown when >= 24 hours) + uiState.daysAndHoursText?.let { daysText -> + Text( + text = daysText, + style = typography.energyMode(), + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f), + modifier = Modifier.padding(top = spacing.small) + ) + } } } } diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingViewModel.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingViewModel.kt index be928cac..596ce104 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingViewModel.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingViewModel.kt @@ -137,10 +137,24 @@ class FastingViewModel( val timerText = "$hours:$minutesStr:$secondsStr" val millisecondsText = "%02d".format(nanoseconds / 10000000) + // Calculate days and hours text for durations >= 24 hours + val daysAndHoursText = if (hours >= 24) { + val days = hours / 24 + val remainingHours = hours % 24 + if (remainingHours == 0L) { + if (days == 1L) "1 day" else "$days days" + } else { + val dayText = if (days == 1L) "1 day" else "$days days" + val hourText = if (remainingHours == 1L) "1 hour" else "$remainingHours hours" + "$dayText, $hourText" + } + } else null + _uiState.update { it.copy( timerText = timerText, - milliseconds = millisecondsText + milliseconds = millisecondsText, + daysAndHoursText = daysAndHoursText ) } } diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/IFastingViewModel.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/IFastingViewModel.kt index a47691b2..37547e8e 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/IFastingViewModel.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/IFastingViewModel.kt @@ -25,6 +25,7 @@ interface IFastingViewModel { val elapsedHours: Double = 0.0, val milliseconds: String = "00", val timerText: String = "00:00:00", + val daysAndHoursText: String? = null, val showGradientBackground: Boolean = true, ) From 30dd2fc1be22da6e3aa6d9ecc342f69ce0d1cf54 Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:55:07 +1100 Subject: [PATCH 5/9] Change indentation from spaces to tabs --- .../fasttrack/screens/fasting/TimeLine.kt | 276 +++++++++--------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt index 5f427b2b..f74a93f2 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/TimeLine.kt @@ -29,11 +29,11 @@ import kotlin.math.min import kotlin.time.Duration.Companion.hours val gaugeColors = listOf( - Color.White, - Color.Green, - Color.Yellow, - Color.Red, - Color.Magenta + Color.White, + Color.Green, + Color.Yellow, + Color.Red, + Color.Magenta ) /** @@ -41,150 +41,150 @@ val gaugeColors = listOf( */ @Composable fun TimeLine( - elapsedHours: Double, - modifier: Modifier = Modifier, - onPhaseClick: (Phase) -> Unit = {} + elapsedHours: Double, + modifier: Modifier = Modifier, + onPhaseClick: (Phase) -> Unit = {} ) { - val padding = 16.dp - val spacing = 4.dp - val barSize = 16.dp - val needleSize = 3.dp - val needleRadius = 4.dp - val slantOffset = 8.dp + val padding = 16.dp + val spacing = 4.dp + val barSize = 16.dp + val needleSize = 3.dp + val needleRadius = 4.dp + val slantOffset = 8.dp - val outlineColor = MaterialTheme.colorScheme.onBackground - - val curPhase = Stages.getCurrentPhase(elapsedHours.hours) - - // Continuous blink animation for current phase - val infiniteTransition = rememberInfiniteTransition(label = "phase_blink") - val blinkProgress by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 1f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 500), - repeatMode = RepeatMode.Reverse - ), - label = "blink_progress" - ) + val outlineColor = MaterialTheme.colorScheme.onBackground + + val curPhase = Stages.getCurrentPhase(elapsedHours.hours) + + // Continuous blink animation for current phase + val infiniteTransition = rememberInfiniteTransition(label = "phase_blink") + val blinkProgress by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 500), + repeatMode = RepeatMode.Reverse + ), + label = "blink_progress" + ) - Canvas( - modifier = modifier - .fillMaxWidth() - .height(padding + barSize) - .pointerInput(curPhase) { - detectTapGestures { offset -> - val paddingPx = padding.toPx() - val spacingPx = spacing.toPx() - val barSizePx = barSize.toPx() - val slantOffsetPx = slantOffset.toPx() - - val phaseWidth = (size.width - (2 * paddingPx) - (Stages.phases.size - 1) * spacingPx) / Stages.phases.size - val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacingPx) + slantOffsetPx - val startOffset = (size.width - totalWidth) / 2f - - val startY = paddingPx - val yOk = abs(offset.y - startY) <= (barSizePx / 2f) - if (yOk) { - Stages.phases.forEachIndexed { index, phase -> - val startX = startOffset + (index * phaseWidth) + (index * spacingPx) - val endX = startX + phaseWidth + slantOffsetPx - if (offset.x in startX..endX) { - onPhaseClick(phase) - return@detectTapGestures - } - } - } - } - } - ) { - val lastPhase = Stages.phases.last() - val lastPhaseHoursWeighted = lastPhase.hours * 1.5f + Canvas( + modifier = modifier + .fillMaxWidth() + .height(padding + barSize) + .pointerInput(curPhase) { + detectTapGestures { offset -> + val paddingPx = padding.toPx() + val spacingPx = spacing.toPx() + val barSizePx = barSize.toPx() + val slantOffsetPx = slantOffset.toPx() + + val phaseWidth = (size.width - (2 * paddingPx) - (Stages.phases.size - 1) * spacingPx) / Stages.phases.size + val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacingPx) + slantOffsetPx + val startOffset = (size.width - totalWidth) / 2f + + val startY = paddingPx + val yOk = abs(offset.y - startY) <= (barSizePx / 2f) + if (yOk) { + Stages.phases.forEachIndexed { index, phase -> + val startX = startOffset + (index * phaseWidth) + (index * spacingPx) + val endX = startX + phaseWidth + slantOffsetPx + if (offset.x in startX..endX) { + onPhaseClick(phase) + return@detectTapGestures + } + } + } + } + } + ) { + val lastPhase = Stages.phases.last() + val lastPhaseHoursWeighted = lastPhase.hours * 1.5f - val slantOffsetPx = slantOffset.toPx() - val barSizePx = barSize.toPx() - - val phaseWidth = (size.width - (2 * padding.toPx()) - (Stages.phases.size - 1) * spacing.toPx()) / Stages.phases.size - val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacing.toPx()) + slantOffsetPx - - val startOffset = (size.width - totalWidth) / 2f + val slantOffsetPx = slantOffset.toPx() + val barSizePx = barSize.toPx() + + val phaseWidth = (size.width - (2 * padding.toPx()) - (Stages.phases.size - 1) * spacing.toPx()) / Stages.phases.size + val totalWidth = (Stages.phases.size * phaseWidth) + ((Stages.phases.size - 1) * spacing.toPx()) + slantOffsetPx + + val startOffset = (size.width - totalWidth) / 2f - Stages.phases.forEachIndexed { index, phase -> - val startX = startOffset + (index * phaseWidth) + (index * spacing.toPx()) - val startY = padding.toPx() + Stages.phases.forEachIndexed { index, phase -> + val startX = startOffset + (index * phaseWidth) + (index * spacing.toPx()) + val startY = padding.toPx() - val rhombusPath = Path().apply { - moveTo(startX + slantOffsetPx, startY - barSizePx / 2) - lineTo(startX + phaseWidth + slantOffsetPx, startY - barSizePx / 2) - lineTo(startX + phaseWidth, startY + barSizePx / 2) - lineTo(startX, startY + barSizePx / 2) - close() - } + val rhombusPath = Path().apply { + moveTo(startX + slantOffsetPx, startY - barSizePx / 2) + lineTo(startX + phaseWidth + slantOffsetPx, startY - barSizePx / 2) + lineTo(startX + phaseWidth, startY + barSizePx / 2) + lineTo(startX, startY + barSizePx / 2) + close() + } - if (curPhase == phase) { - drawPath( - path = rhombusPath, - color = gaugeColors[index], - style = Fill - ) - - // Continuously animate between orange and yellow - val baseOutlineColor = Color(0xFFE67E22) // Orange - val blinkColor = Color.Yellow - val currentOutlineColor = lerp(baseOutlineColor, blinkColor, blinkProgress) - - drawPath( - path = rhombusPath, - color = currentOutlineColor, - style = Stroke(width = 3.dp.toPx()) - ) - } else { - drawPath( - path = rhombusPath, - color = gaugeColors[index], - style = Fill - ) - - drawPath( - path = rhombusPath, - color = outlineColor, - style = Stroke(width = 2.dp.toPx()) - ) - } - } + if (curPhase == phase) { + drawPath( + path = rhombusPath, + color = gaugeColors[index], + style = Fill + ) + + // Continuously animate between orange and yellow + val baseOutlineColor = Color(0xFFE67E22) // Orange + val blinkColor = Color.Yellow + val currentOutlineColor = lerp(baseOutlineColor, blinkColor, blinkProgress) + + drawPath( + path = rhombusPath, + color = currentOutlineColor, + style = Stroke(width = 3.dp.toPx()) + ) + } else { + drawPath( + path = rhombusPath, + color = gaugeColors[index], + style = Fill + ) + + drawPath( + path = rhombusPath, + color = outlineColor, + style = Stroke(width = 2.dp.toPx()) + ) + } + } - // Draw the needle - if (elapsedHours > 0) { - val curPhaseIndex = Stages.phases.indexOf(curPhase) - val nextPhaseHours: Float = if (curPhaseIndex + 1 < Stages.phases.size) { - val nextPhase = Stages.phases[curPhaseIndex + 1] - nextPhase.hours.toFloat() - } else { - lastPhaseHoursWeighted - } - val phaseLength = (nextPhaseHours - curPhase.hours) - val timeIntoPhase = elapsedHours - curPhase.hours + // Draw the needle + if (elapsedHours > 0) { + val curPhaseIndex = Stages.phases.indexOf(curPhase) + val nextPhaseHours: Float = if (curPhaseIndex + 1 < Stages.phases.size) { + val nextPhase = Stages.phases[curPhaseIndex + 1] + nextPhase.hours.toFloat() + } else { + lastPhaseHoursWeighted + } + val phaseLength = (nextPhaseHours - curPhase.hours) + val timeIntoPhase = elapsedHours - curPhase.hours - val percent = min(timeIntoPhase / phaseLength, 1.0) + val percent = min(timeIntoPhase / phaseLength, 1.0) - val halfPadding = padding.toPx() / 2f + val halfPadding = padding.toPx() / 2f - val startX = startOffset + (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) - val x = (startX + (phaseWidth * percent)).toFloat() + val startX = startOffset + (curPhaseIndex * phaseWidth) + (curPhaseIndex * spacing.toPx()) + val x = (startX + (phaseWidth * percent)).toFloat() - drawLine( - color = Color.DarkGray, - start = Offset(x, halfPadding), - end = Offset(x, barSize.toPx() + halfPadding), - strokeWidth = needleSize.toPx(), - cap = StrokeCap.Square - ) + drawLine( + color = Color.DarkGray, + start = Offset(x, halfPadding), + end = Offset(x, barSize.toPx() + halfPadding), + strokeWidth = needleSize.toPx(), + cap = StrokeCap.Square + ) - drawCircle( - color = Color.DarkGray, - radius = needleRadius.toPx(), - center = Offset(x, barSize.toPx() + halfPadding + needleRadius.toPx()) - ) - } - } + drawCircle( + color = Color.DarkGray, + radius = needleRadius.toPx(), + center = Offset(x, barSize.toPx() + halfPadding + needleRadius.toPx()) + ) + } + } } \ No newline at end of file From f5947cf2f43bafac95acda198e2abb17188c6510 Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:57:47 +1100 Subject: [PATCH 6/9] Refactor functions to satisfy static code test --- .../screens/fasting/FastingScreen.kt | 174 +++++++++++------- 1 file changed, 103 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt index 03d67542..e2208661 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt @@ -351,41 +351,10 @@ private fun FastDetailsContent( Spacer(modifier = Modifier.weight(1f)) // Phase Information - Column( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = spacing.medium) - ) { - // Fat Burn Phase - StageInfo( - onShowInfoDialog = onShowInfoDialog, - titleRes = R.string.info_dialog_fat_burn_title, - contentRes = R.string.info_dialog_fat_burn_content, - labelRes = R.string.fast_fat_burn_label, - timeText = uiState.fatBurnTime, - stageState = uiState.fatBurnStageState - ) - - // Ketosis Phase - StageInfo( - onShowInfoDialog = onShowInfoDialog, - titleRes = R.string.info_dialog_ketosis_title, - contentRes = R.string.info_dialog_ketosis_content, - labelRes = R.string.fast_ketosis_label, - timeText = uiState.ketosisTime, - stageState = uiState.ketosisStageState - ) - - // Autophagy Phase - StageInfo( - onShowInfoDialog = onShowInfoDialog, - titleRes = R.string.info_dialog_autophagy_title, - contentRes = R.string.info_dialog_autophagy_content, - labelRes = R.string.fast_autophagy_label, - timeText = uiState.autophagyTime, - stageState = uiState.autophagyStageState - ) - } + PhaseInformationSection( + uiState = uiState, + onShowInfoDialog = onShowInfoDialog + ) Row(modifier = Modifier .fillMaxWidth() @@ -406,47 +375,110 @@ private fun FastDetailsContent( } // Bottom Controls Row - Row( + FastActionButtons( + isFasting = uiState.isFasting, + onShowEndFastConfirmation = onShowEndFastConfirmation, + onShowStartFastSelector = onShowStartFastSelector, + viewModel = viewModel, modifier = Modifier .align(Alignment.Bottom) .wrapContentHeight() - .padding(top = spacing.medium), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically + .padding(top = spacing.medium) + ) + } + } +} + +@Composable +private fun PhaseInformationSection( + uiState: IFastingViewModel.FastingUiState, + onShowInfoDialog: (Int, Int) -> Unit +) { + val spacing = fastingSpacing() + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = spacing.medium) + ) { + // Fat Burn Phase + StageInfo( + onShowInfoDialog = onShowInfoDialog, + titleRes = R.string.info_dialog_fat_burn_title, + contentRes = R.string.info_dialog_fat_burn_content, + labelRes = R.string.fast_fat_burn_label, + timeText = uiState.fatBurnTime, + stageState = uiState.fatBurnStageState + ) + + // Ketosis Phase + StageInfo( + onShowInfoDialog = onShowInfoDialog, + titleRes = R.string.info_dialog_ketosis_title, + contentRes = R.string.info_dialog_ketosis_content, + labelRes = R.string.fast_ketosis_label, + timeText = uiState.ketosisTime, + stageState = uiState.ketosisStageState + ) + + // Autophagy Phase + StageInfo( + onShowInfoDialog = onShowInfoDialog, + titleRes = R.string.info_dialog_autophagy_title, + contentRes = R.string.info_dialog_autophagy_content, + labelRes = R.string.fast_autophagy_label, + timeText = uiState.autophagyTime, + stageState = uiState.autophagyStageState + ) + } +} + +@Composable +private fun FastActionButtons( + isFasting: Boolean, + onShowEndFastConfirmation: () -> Unit, + onShowStartFastSelector: () -> Unit, + viewModel: IFastingViewModel, + modifier: Modifier = Modifier +) { + val spacing = fastingSpacing() + + Row( + modifier = modifier, + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + // Debug Button (only in debug builds) + if (BuildConfig.DEBUG) { + FloatingActionButton( + onClick = { viewModel.debugIncreaseFastingTimeByOneHour() }, + modifier = Modifier.padding(end = spacing.medium) ) { - // Debug Button (only in debug builds) - if (BuildConfig.DEBUG) { - FloatingActionButton( - onClick = { viewModel.debugIncreaseFastingTimeByOneHour() }, - modifier = Modifier.padding(end = spacing.medium) - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = stringResource(id = R.string.debug_add_hour_button) - ) - } - } + Icon( + imageVector = Icons.Default.Add, + contentDescription = stringResource(id = R.string.debug_add_hour_button) + ) + } + } - // Start/Stop Button - if (uiState.isFasting) { - FloatingActionButton( - onClick = onShowEndFastConfirmation, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_fast_stop), - contentDescription = stringResource(id = R.string.stop_fast_button_description) - ) - } - } else { - FloatingActionButton( - onClick = onShowStartFastSelector, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_start_fast), - contentDescription = stringResource(id = R.string.start_fast_button_description) - ) - } - } + // Start/Stop Button + if (isFasting) { + FloatingActionButton( + onClick = onShowEndFastConfirmation, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_fast_stop), + contentDescription = stringResource(id = R.string.stop_fast_button_description) + ) + } + } else { + FloatingActionButton( + onClick = onShowStartFastSelector, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_start_fast), + contentDescription = stringResource(id = R.string.start_fast_button_description) + ) } } } From 4960c8a5466925f6bf3a351a2df9d40cc2d12891 Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:00:38 +1100 Subject: [PATCH 7/9] Further refactor --- .../screens/fasting/FastingScreen.kt | 156 ++++++++++-------- 1 file changed, 91 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt index e2208661..5bdab2fc 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt @@ -232,22 +232,9 @@ private fun FastHeadingContent( uiState: IFastingViewModel.FastingUiState, modifier: Modifier = Modifier ) { - val scope = rememberCoroutineScope() - val tooltipState = rememberTooltipState() val spacing = fastingSpacing() val typography = fastingTypography() - @StringRes - var phaseTooltipResId by remember { mutableStateOf(null) } - - LaunchedEffect(tooltipState.isVisible) { - if (tooltipState.isVisible) { - delay(4.seconds) - tooltipState.dismiss() - phaseTooltipResId = null - } - } - Column( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally @@ -262,29 +249,9 @@ private fun FastHeadingContent( modifier = Modifier.padding(bottom = spacing.small) ) - TooltipBox( - positionProvider = TooltipDefaults - .rememberTooltipPositionProvider(TooltipAnchorPosition.Below), - tooltip = { - phaseTooltipResId?.let { stringRes -> - PlainTooltip { Text(stringResource(stringRes)) } - } - }, - state = tooltipState, - ) { - TimeLine( - elapsedHours = uiState.elapsedHours, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = spacing.medium), - onPhaseClick = { phase -> - phaseTooltipResId = phase.title - scope.launch { - tooltipState.show() - } - } - ) - } + TimeLineWithTooltip( + elapsedHours = uiState.elapsedHours + ) // Energy Mode Text( @@ -297,37 +264,96 @@ private fun FastHeadingContent( Spacer(modifier = Modifier.size(height = spacing.large, width = 1.dp)) - // Timer - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(bottom = spacing.large) - ) { - Row( - verticalAlignment = Alignment.Bottom - ) { - Text( - text = uiState.timerText, - style = typography.timerText(), - color = MaterialTheme.colorScheme.onBackground, - fontWeight = FontWeight.Bold, - ) - Text( - text = uiState.milliseconds, - style = typography.timerMilliseconds(), - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.padding(start = spacing.small, bottom = spacing.small) - ) - } + TimerDisplay( + timerText = uiState.timerText, + milliseconds = uiState.milliseconds, + daysAndHoursText = uiState.daysAndHoursText + ) + } +} - // Days and hours text (shown when >= 24 hours) - uiState.daysAndHoursText?.let { daysText -> - Text( - text = daysText, - style = typography.energyMode(), - color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f), - modifier = Modifier.padding(top = spacing.small) - ) +@Composable +private fun TimeLineWithTooltip( + elapsedHours: Double +) { + val scope = rememberCoroutineScope() + val tooltipState = rememberTooltipState() + val spacing = fastingSpacing() + + @StringRes + var phaseTooltipResId by remember { mutableStateOf(null) } + + LaunchedEffect(tooltipState.isVisible) { + if (tooltipState.isVisible) { + delay(4.seconds) + tooltipState.dismiss() + phaseTooltipResId = null + } + } + + TooltipBox( + positionProvider = TooltipDefaults + .rememberTooltipPositionProvider(TooltipAnchorPosition.Below), + tooltip = { + phaseTooltipResId?.let { stringRes -> + PlainTooltip { Text(stringResource(stringRes)) } } + }, + state = tooltipState, + ) { + TimeLine( + elapsedHours = elapsedHours, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = spacing.medium), + onPhaseClick = { phase -> + phaseTooltipResId = phase.title + scope.launch { + tooltipState.show() + } + } + ) + } +} + +@Composable +private fun TimerDisplay( + timerText: String, + milliseconds: String, + daysAndHoursText: String? +) { + val spacing = fastingSpacing() + val typography = fastingTypography() + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(bottom = spacing.large) + ) { + Row( + verticalAlignment = Alignment.Bottom + ) { + Text( + text = timerText, + style = typography.timerText(), + color = MaterialTheme.colorScheme.onBackground, + fontWeight = FontWeight.Bold, + ) + Text( + text = milliseconds, + style = typography.timerMilliseconds(), + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.padding(start = spacing.small, bottom = spacing.small) + ) + } + + // Days and hours text (shown when >= 24 hours) + daysAndHoursText?.let { daysText -> + Text( + text = daysText, + style = typography.energyMode(), + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f), + modifier = Modifier.padding(top = spacing.small) + ) } } } From 78254a5635b1f46a7f3b2f7ac2939c53cbd4e80d Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:40:25 +1100 Subject: [PATCH 8/9] More refactoring --- .../screens/fasting/FastingScreen.kt | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt index 5bdab2fc..b591656d 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt @@ -367,9 +367,6 @@ private fun FastDetailsContent( onShowStartFastSelector: () -> Unit, modifier: Modifier = Modifier ) { - val spacing = fastingSpacing() - val typography = fastingTypography() - Column( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally, @@ -382,36 +379,58 @@ private fun FastDetailsContent( onShowInfoDialog = onShowInfoDialog ) - Row(modifier = Modifier - .fillMaxWidth() - .weight(2f)) { - // Stage Description - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .verticalScroll(rememberScrollState()) - ) { - Text( - text = uiState.stageDescription, - style = typography.stageDescription(), - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.padding(top = spacing.medium, end = spacing.medium) - ) - } + StageDescriptionAndActions( + stageDescription = uiState.stageDescription, + isFasting = uiState.isFasting, + onShowEndFastConfirmation = onShowEndFastConfirmation, + onShowStartFastSelector = onShowStartFastSelector, + viewModel = viewModel, + modifier = Modifier + .fillMaxWidth() + .weight(2f) + ) + } +} - // Bottom Controls Row - FastActionButtons( - isFasting = uiState.isFasting, - onShowEndFastConfirmation = onShowEndFastConfirmation, - onShowStartFastSelector = onShowStartFastSelector, - viewModel = viewModel, - modifier = Modifier - .align(Alignment.Bottom) - .wrapContentHeight() - .padding(top = spacing.medium) +@Composable +private fun StageDescriptionAndActions( + stageDescription: String, + isFasting: Boolean, + onShowEndFastConfirmation: () -> Unit, + onShowStartFastSelector: () -> Unit, + viewModel: IFastingViewModel, + modifier: Modifier = Modifier +) { + val spacing = fastingSpacing() + val typography = fastingTypography() + + Row(modifier = modifier) { + // Stage Description + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = stageDescription, + style = typography.stageDescription(), + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.padding(top = spacing.medium, end = spacing.medium) ) } + + // Bottom Controls Row + FastActionButtons( + isFasting = isFasting, + onShowEndFastConfirmation = onShowEndFastConfirmation, + onShowStartFastSelector = onShowStartFastSelector, + viewModel = viewModel, + modifier = Modifier + .align(Alignment.Bottom) + .wrapContentHeight() + .padding(top = spacing.medium) + ) } } From 59efc697baf15554fc071526406847735cf80696 Mon Sep 17 00:00:00 2001 From: 825i <58630825+825i@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:00:58 +1100 Subject: [PATCH 9/9] Second final refactor --- .../apps/fasttrack/screens/fasting/FastingScreen.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt index b591656d..21eade42 100644 --- a/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt +++ b/app/src/main/java/com/darkrockstudios/apps/fasttrack/screens/fasting/FastingScreen.kt @@ -380,8 +380,7 @@ private fun FastDetailsContent( ) StageDescriptionAndActions( - stageDescription = uiState.stageDescription, - isFasting = uiState.isFasting, + uiState = uiState, onShowEndFastConfirmation = onShowEndFastConfirmation, onShowStartFastSelector = onShowStartFastSelector, viewModel = viewModel, @@ -394,8 +393,7 @@ private fun FastDetailsContent( @Composable private fun StageDescriptionAndActions( - stageDescription: String, - isFasting: Boolean, + uiState: IFastingViewModel.FastingUiState, onShowEndFastConfirmation: () -> Unit, onShowStartFastSelector: () -> Unit, viewModel: IFastingViewModel, @@ -413,7 +411,7 @@ private fun StageDescriptionAndActions( .verticalScroll(rememberScrollState()) ) { Text( - text = stageDescription, + text = uiState.stageDescription, style = typography.stageDescription(), color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.padding(top = spacing.medium, end = spacing.medium) @@ -422,7 +420,7 @@ private fun StageDescriptionAndActions( // Bottom Controls Row FastActionButtons( - isFasting = isFasting, + isFasting = uiState.isFasting, onShowEndFastConfirmation = onShowEndFastConfirmation, onShowStartFastSelector = onShowStartFastSelector, viewModel = viewModel,