The bar read "31% of 5h", which is the one thing about the window a reader already knows. What decides whether to start something now is how long what is left has to last: 80% with twenty minutes to go and 80% with four hours to go are opposite answers, and the second number was a screen away on the usage screen. It now reads "31% - 2h 36m left", and the countdown is driven by a clock the refresh loop advances rather than computed at draw time. A percentage that comes back unchanged is an equal value, so Compose skips the recomposition -- a "left" recomputed only when the quota happens to move would have sat at a stale figure for hours while looking live. A window can arrive with no reset time, so that keeps its own wording: "reset time unknown" rather than "refresh soon", which would be a recommendation nothing measured. Under a minute, including past the end, is "refresh soon" -- "0m left" reads as a measurement. The span arithmetic was already on the usage screen, so it moves into `ResetCountdown.kt` and both callers supply their own sentence. That screen still reads "resets in 2h 37m" and "resets in 5d 21h", checked on the emulator alongside the bar it was not part of changing. The fill is blue rather than the scheme's primary: the bar sits under every session header, on a screen somebody opened to do something else, and it reports a quantity rather than a verdict. The usage screen is still where the same number turns yellow and then red, for a reader who went there to be told where the limits are. Also declares this project's resources for Dev Updater, whose declaration schema changed in d27b5a3: `resources.ron` says ai-app keeps its state as `ai-app`, so the Uninstall dialog offers the real directories instead of saying it cannot tell where they are. Only the name, because both XDG places are the conventional ones. What that dialog's config toggle would delete includes the CA under `certs`, which strands every phone running an APK pinned to it -- noted where somebody would be standing when it matters.
38 lines
1.3 KiB
Kotlin
38 lines
1.3 KiB
Kotlin
package com.example.aiapp
|
|
|
|
import java.time.Duration
|
|
import java.time.OffsetDateTime
|
|
|
|
/**
|
|
* How long is left in a usage window.
|
|
*
|
|
* Shared by the session bar and the usage screen: the arithmetic is the same in both and only the
|
|
* sentence around it differs, so this returns the span on its own and leaves the wording to the
|
|
* caller.
|
|
*/
|
|
|
|
/** "1d 4h", "3h 12m", "12m" -- the span alone, with no leading or trailing words. */
|
|
fun formatSpan(until: Duration): String =
|
|
when {
|
|
until.toHours() >= 24 -> "${until.toDays()}d ${until.toHours() % 24}h"
|
|
until.toHours() > 0 -> "${until.toHours()}h ${until.toMinutes() % 60}m"
|
|
else -> "${until.toMinutes()}m"
|
|
}
|
|
|
|
/**
|
|
* Time from [now] until [resetsAt], or null when that timestamp cannot be read.
|
|
*
|
|
* Null rather than a zero duration, because a string this app failed to parse is not a window that
|
|
* has just run out: a caller given zero for both would tell the reader to refresh on the strength
|
|
* of something nobody measured.
|
|
*
|
|
* [now] is a parameter rather than read here so a caller can drive it from state and have the
|
|
* countdown recompute on its own schedule.
|
|
*/
|
|
fun remainingUntil(resetsAt: String, now: OffsetDateTime): Duration? =
|
|
try {
|
|
Duration.between(now, OffsetDateTime.parse(resetsAt))
|
|
} catch (_: Exception) {
|
|
null
|
|
}
|