feat(wear): expand Wear OS with quick timer creation, timer detail, dismiss/snooze actions, navigation
This commit is contained in:
parent
3ac658a89e
commit
bf2f7cde50
@ -26,46 +26,107 @@ class WearMainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Navigation State ─────────────────────────────────────────
|
||||||
|
|
||||||
|
enum class WearScreen { TIMELINE, QUICK_TIMER, DETAIL }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WearApp() {
|
fun WearApp() {
|
||||||
|
var currentScreen by remember { mutableStateOf(WearScreen.TIMELINE) }
|
||||||
|
var selectedTimer by remember { mutableStateOf<WearTimer?>(null) }
|
||||||
|
val timers = remember { mutableStateListOf<WearTimer>() }
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colors = Colors(
|
colors = Colors(
|
||||||
primary = Color(0xFF6C5CE7),
|
primary = Color(0xFF5B8DEE),
|
||||||
secondary = Color(0xFF00D2FF),
|
secondary = Color(0xFF00D2FF),
|
||||||
background = Color(0xFF0A0A0F),
|
background = Color(0xFF0A0B0F),
|
||||||
surface = Color(0xFF14141F),
|
surface = Color(0xFF1A1B26),
|
||||||
error = Color(0xFFFF5252),
|
error = Color(0xFFFF4757),
|
||||||
onPrimary = Color.White,
|
onPrimary = Color.White,
|
||||||
onSecondary = Color.White,
|
onSecondary = Color.White,
|
||||||
onBackground = Color(0xFFEEEEFF),
|
onBackground = Color(0xFFE8ECF4),
|
||||||
onSurface = Color(0xFFEEEEFF),
|
onSurface = Color(0xFFE8ECF4),
|
||||||
onError = Color.White,
|
onError = Color.White,
|
||||||
onSurfaceVariant = Color(0xFFAAAACC),
|
onSurfaceVariant = Color(0xFF8B92A8),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
WearTimelineScreen()
|
when (currentScreen) {
|
||||||
|
WearScreen.TIMELINE -> WearTimelineScreen(
|
||||||
|
timers = timers,
|
||||||
|
onTimerClick = { timer ->
|
||||||
|
selectedTimer = timer
|
||||||
|
currentScreen = WearScreen.DETAIL
|
||||||
|
},
|
||||||
|
onQuickTimerClick = { currentScreen = WearScreen.QUICK_TIMER }
|
||||||
|
)
|
||||||
|
WearScreen.QUICK_TIMER -> WearQuickTimerScreen(
|
||||||
|
onCreateTimer = { timer ->
|
||||||
|
timers.add(timer)
|
||||||
|
currentScreen = WearScreen.TIMELINE
|
||||||
|
},
|
||||||
|
onBack = { currentScreen = WearScreen.TIMELINE }
|
||||||
|
)
|
||||||
|
WearScreen.DETAIL -> {
|
||||||
|
selectedTimer?.let { timer ->
|
||||||
|
WearTimerDetailScreen(
|
||||||
|
timer = timer,
|
||||||
|
onDismiss = {
|
||||||
|
timers.removeAll { it.id == timer.id }
|
||||||
|
currentScreen = WearScreen.TIMELINE
|
||||||
|
},
|
||||||
|
onSnooze = { minutes ->
|
||||||
|
val idx = timers.indexOfFirst { it.id == timer.id }
|
||||||
|
if (idx >= 0) {
|
||||||
|
timers[idx] = timer.copy(
|
||||||
|
remainingSeconds = minutes * 60.0,
|
||||||
|
state = "snoozed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
currentScreen = WearScreen.TIMELINE
|
||||||
|
},
|
||||||
|
onBack = { currentScreen = WearScreen.TIMELINE }
|
||||||
|
)
|
||||||
|
} ?: run { currentScreen = WearScreen.TIMELINE }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
// ── Timeline Screen ──────────────────────────────────────────
|
||||||
fun WearTimelineScreen() {
|
|
||||||
val timers = remember { mutableStateListOf<WearTimer>() }
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WearTimelineScreen(
|
||||||
|
timers: List<WearTimer>,
|
||||||
|
onTimerClick: (WearTimer) -> Unit,
|
||||||
|
onQuickTimerClick: () -> Unit
|
||||||
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
timeText = { TimeText() },
|
timeText = { TimeText() },
|
||||||
vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }
|
vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }
|
||||||
) {
|
) {
|
||||||
if (timers.isEmpty()) {
|
ScalingLazyColumn(
|
||||||
WearEmptyState()
|
modifier = Modifier.fillMaxSize(),
|
||||||
} else {
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
ScalingLazyColumn(
|
) {
|
||||||
modifier = Modifier.fillMaxSize(),
|
if (timers.isEmpty()) {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
item { WearEmptyState() }
|
||||||
) {
|
} else {
|
||||||
items(timers) { timer ->
|
items(timers) { timer ->
|
||||||
WearTimerChip(timer)
|
WearTimerChip(timer, onClick = { onTimerClick(timer) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Quick timer button at bottom
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
CompactChip(
|
||||||
|
onClick = onQuickTimerClick,
|
||||||
|
label = { Text("+ Quick Timer", fontSize = 12.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFF5B8DEE)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,11 +160,182 @@ private fun WearEmptyState() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Quick Timer Screen ───────────────────────────────────────
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WearTimerChip(timer: WearTimer) {
|
fun WearQuickTimerScreen(
|
||||||
|
onCreateTimer: (WearTimer) -> Unit,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
val presets = listOf(
|
||||||
|
"1 min" to 60.0,
|
||||||
|
"3 min" to 180.0,
|
||||||
|
"5 min" to 300.0,
|
||||||
|
"10 min" to 600.0,
|
||||||
|
"15 min" to 900.0,
|
||||||
|
"25 min" to 1500.0,
|
||||||
|
"30 min" to 1800.0,
|
||||||
|
"60 min" to 3600.0
|
||||||
|
)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
timeText = { TimeText() },
|
||||||
|
vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }
|
||||||
|
) {
|
||||||
|
ScalingLazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Quick Timer",
|
||||||
|
color = Color(0xFFE8ECF4),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
items(presets) { (label, seconds) ->
|
||||||
|
Chip(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
onCreateTimer(
|
||||||
|
WearTimer(
|
||||||
|
id = java.util.UUID.randomUUID().toString(),
|
||||||
|
label = "Timer ($label)",
|
||||||
|
urgency = "standard",
|
||||||
|
remainingSeconds = seconds,
|
||||||
|
state = "active"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { Text(label, fontSize = 14.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFF1A1B26)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
CompactChip(
|
||||||
|
onClick = onBack,
|
||||||
|
label = { Text("Cancel", fontSize = 12.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFF2A2D3A)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Timer Detail Screen ──────────────────────────────────────
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WearTimerDetailScreen(
|
||||||
|
timer: WearTimer,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSnooze: (Int) -> Unit,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
timeText = { TimeText() },
|
||||||
|
vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }
|
||||||
|
) {
|
||||||
|
ScalingLazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = timer.label,
|
||||||
|
color = Color(0xFFE8ECF4),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = timer.formattedRemaining,
|
||||||
|
color = urgencyColor(timer.urgency),
|
||||||
|
fontSize = 36.sp,
|
||||||
|
fontWeight = FontWeight.Thin,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss
|
||||||
|
item {
|
||||||
|
Chip(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onDismiss,
|
||||||
|
label = { Text("Dismiss", fontSize = 14.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFFFF4757)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snooze options
|
||||||
|
item {
|
||||||
|
Chip(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = { onSnooze(5) },
|
||||||
|
label = { Text("Snooze 5 min", fontSize = 14.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFF2A2D3A)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Chip(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = { onSnooze(10) },
|
||||||
|
label = { Text("Snooze 10 min", fontSize = 14.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFF2A2D3A)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
CompactChip(
|
||||||
|
onClick = onBack,
|
||||||
|
label = { Text("Back", fontSize = 12.sp) },
|
||||||
|
colors = ChipDefaults.chipColors(
|
||||||
|
backgroundColor = Color(0xFF2A2D3A)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun urgencyColor(urgency: String): Color {
|
||||||
|
return when (urgency) {
|
||||||
|
"critical" -> Color(0xFFFF4757)
|
||||||
|
"important" -> Color(0xFFFF9F43)
|
||||||
|
"gentle" -> Color(0xFF2ED573)
|
||||||
|
"passive" -> Color(0xFF8B92A8)
|
||||||
|
else -> Color(0xFF5B8DEE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Timer Chip ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WearTimerChip(timer: WearTimer, onClick: () -> Unit = {}) {
|
||||||
Chip(
|
Chip(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = { /* navigate to detail */ },
|
onClick = onClick,
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = timer.label,
|
text = timer.label,
|
||||||
@ -120,19 +352,8 @@ fun WearTimerChip(timer: WearTimer) {
|
|||||||
color = Color(0xFF6C5CE7)
|
color = Color(0xFF6C5CE7)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
icon = {
|
|
||||||
Text(
|
|
||||||
text = when (timer.urgency) {
|
|
||||||
"critical" -> "🔴"
|
|
||||||
"important" -> "🟠"
|
|
||||||
"gentle" -> "🟢"
|
|
||||||
else -> "🔵"
|
|
||||||
},
|
|
||||||
fontSize = 16.sp
|
|
||||||
)
|
|
||||||
},
|
|
||||||
colors = ChipDefaults.chipColors(
|
colors = ChipDefaults.chipColors(
|
||||||
backgroundColor = Color(0xFF14141F)
|
backgroundColor = Color(0xFF1A1B26)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user