Skip to content
Merged
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
@@ -1,15 +1,25 @@
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
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
Expand All @@ -36,30 +46,49 @@ fun TimeLine(
onPhaseClick: (Phase) -> Unit = {}
) {
val padding = 16.dp
val spacing = 18.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"
)

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 availableWidth = size.width - paddingPx
val phaseWidth = (availableWidth / Stages.phases.size) - spacingPx
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 = (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
Expand All @@ -72,54 +101,54 @@ 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 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 curPhase = Stages.getCurrentPhase(elapsedHours.hours)

// Draw the bubbles (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()

// 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
)
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()
}

// Current phase - filled
drawLine(
if (curPhase == phase) {
drawPath(
path = rhombusPath,
color = gaugeColors[index],
start = Offset(startX, startY),
end = Offset(startX + phaseWidth, startY),
strokeWidth = barSize.toPx() * 0.7f,
cap = StrokeCap.Round
style = Fill
)
} 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
// 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())
)

// Phase color
drawLine(
} else {
drawPath(
path = rhombusPath,
color = gaugeColors[index],
start = Offset(startX, startY),
end = Offset(startX + phaseWidth, startY),
strokeWidth = barSize.toPx() * 0.8f, // Slightly thinner
cap = StrokeCap.Round
style = Fill
)

drawPath(
path = rhombusPath,
color = outlineColor,
style = Stroke(width = 2.dp.toPx())
)
}
}
Expand All @@ -140,10 +169,9 @@ 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
drawLine(
color = Color.DarkGray,
start = Offset(x, halfPadding),
Expand All @@ -152,12 +180,11 @@ fun TimeLine(
cap = StrokeCap.Square
)

// Draw needle circle
drawCircle(
color = Color.DarkGray,
radius = needleRadius.toPx(),
center = Offset(x, barSize.toPx() + halfPadding + needleRadius.toPx())
)
}
}
}
}
Loading