1package soy.iko.opencode.ui.components
3import androidx.compose.runtime.Composable
4import androidx.compose.runtime.LaunchedEffect
5import androidx.compose.runtime.getValue
6import androidx.compose.runtime.mutableLongStateOf
7import androidx.compose.runtime.remember
8import androidx.compose.runtime.setValue
9import androidx.lifecycle.compose.LocalLifecycleOwner
10import kotlinx.coroutines.delay
11import java.util.concurrent.TimeUnit
13/** Formats an epoch-millis timestamp as a short relative string (e.g. "3m", "2h", "5d"). */
14fun relativeTime(epochMillis: Long?): String {
15 if (epochMillis == null || epochMillis <= 0) return ""
16 val diff = System.currentTimeMillis() - epochMillis
17 if (diff < 0) return "now"
18 val minutes = TimeUnit.MILLISECONDS.toMinutes(diff)
19 if (minutes < 1) return "now"
20 if (minutes < 60) return "${minutes}m"
21 val hours = TimeUnit.MILLISECONDS.toHours(diff)
22 if (hours < 24) return "${hours}h"
23 val days = TimeUnit.MILLISECONDS.toDays(diff)
24 if (days < 7) return "${days}d"
26 if (weeks < 5) return "${weeks}w"
27 val months = days / 30
28 if (months < 12) return "${months}mo"
29 val years = (days / 365).coerceAtLeast(1)
34 * Returns a relative-time string for [epochMillis] that auto-refreshes so the label
35 * doesn't go stale (e.g. "3m" → "4m" → "5m" while the screen is visible). Re-evaluates
36 * every [intervalMs] (default 30 s — coarse enough for battery, fine enough for the
37 * smallest unit we show, which is minutes). Pauses the timer when the lifecycle is
38 * not at least resumed (screen off / app backgrounded) to avoid battery drain.
41fun rememberRelativeTime(epochMillis: Long?, intervalMs: Long = 30_000L): String {
42 var tick by remember { mutableLongStateOf(0L) }
43 val lifecycleOwner = LocalLifecycleOwner.current
44 LaunchedEffect(epochMillis, lifecycleOwner) {
45 if (epochMillis == null) return@LaunchedEffect
46 // Observe lifecycle to pause the timer when the screen isn't visible.
47 val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
48 if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
52 lifecycleOwner.lifecycle.addObserver(observer)
59 lifecycleOwner.lifecycle.removeObserver(observer)
62 // Read tick so Compose tracks it as a dependency and recomposes when it changes.
63 @Suppress("UNUSED_EXPRESSION") tick
64 return relativeTime(epochMillis)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list