Skip to content

Commit d8246cc

Browse files
authored
Merge pull request #1 from xxnjdlys/agent/wear-usage-icons
fix: correct wear usage states and app icons
2 parents db000af + b7c3888 commit d8246cc

18 files changed

Lines changed: 123 additions & 35 deletions

File tree

apps/android-wear/README.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Codex Usage Wear OS
22

3-
Wear OS dashboard for local Codex usage. The app reads live JSON from the same-LAN service and falls back to the packaged snapshot if the service is unavailable.
3+
Wear OS dashboard for local Codex usage. The app reads live JSON from the same-LAN service and shows a neutral no-data state until the first request succeeds.
44

55
## Configure Live Data
66

@@ -79,11 +79,7 @@ CodexUsageHttpReader
7979
UsageDashboard
8080
```
8181

82-
The fallback asset is:
83-
84-
```text
85-
apps/android-wear/src/main/assets/codex_usage_snapshot.json
86-
```
82+
Until the first live request succeeds, the dashboard shows a neutral no-data state. Network failures keep the last successfully fetched live snapshot and never substitute packaged usage values.
8783

8884
## Troubleshooting
8985

@@ -106,5 +102,5 @@ If the service logs `using latest session day`, Codex has not created today's lo
106102
- Main context number: `lastTokenUsage.totalTokens / contextWindowTokens`.
107103
- Model metric: `currentModel`, falling back to `limitId`.
108104
- Plan metric: `planType`.
109-
- Quota rings: 5h and 7d window usage from `primaryWindow` and `secondaryWindow`.
105+
- Quota rings: 5h and 7d windows selected by each rate window's `windowMinutes`, regardless of whether the service places it in `primaryWindow` or `secondaryWindow`.
110106
- Trend bars: hourly token-consumption trend from `trendPercent`.

apps/android-wear/src/main/java/com/yummbj/codex/usage/wear/MainActivity.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,23 @@ import androidx.compose.runtime.getValue
77
import androidx.compose.runtime.mutableStateOf
88
import androidx.compose.runtime.remember
99
import androidx.compose.runtime.setValue
10-
import com.yummbj.codex.usage.wear.data.CodexUsageAssetReader
1110
import com.yummbj.codex.usage.wear.data.CodexUsageHttpReader
1211
import com.yummbj.codex.usage.wear.data.CodexUsageServiceConfig
1312
import com.yummbj.codex.usage.wear.ui.CodexUsageWearApp
1413

1514
class MainActivity : ComponentActivity() {
1615
override fun onCreate(savedInstanceState: Bundle?) {
1716
super.onCreate(savedInstanceState)
18-
val codexUsageSnapshot = CodexUsageAssetReader.read(this)
1917
setContent {
2018
var serviceHost by remember { mutableStateOf(CodexUsageServiceConfig.readHost(this)) }
2119
CodexUsageWearApp(
22-
codexUsageSnapshot = codexUsageSnapshot,
2320
serviceHost = serviceHost,
2421
onSaveServiceHost = { host ->
2522
CodexUsageServiceConfig.saveHost(this, host)
2623
serviceHost = CodexUsageServiceConfig.readHost(this)
2724
},
2825
onRefreshSnapshot = {
29-
CodexUsageHttpReader.read(CodexUsageServiceConfig.usageUrl(serviceHost)) ?: CodexUsageAssetReader.read(this)
26+
CodexUsageHttpReader.read(CodexUsageServiceConfig.usageUrl(serviceHost))
3027
},
3128
)
3229
}

apps/android-wear/src/main/java/com/yummbj/codex/usage/wear/model/CodexUsageExport.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ data class CodexUsageExport(
1616
fun toUsageSnapshot(nowEpochSeconds: Long): UsageSnapshot {
1717
val lastActivitySecondsAgo = (nowEpochSeconds - lastActivityEpochSeconds).coerceAtLeast(0L).toInt()
1818
val primaryPercent = primaryWindow?.usedPercent ?: 0.0
19+
val rateWindows = listOfNotNull(primaryWindow, secondaryWindow)
1920
val status = when {
2021
primaryPercent >= 100.0 -> UsageStatus.LimitReached
2122
primaryPercent >= 80.0 -> UsageStatus.NearLimit
@@ -33,8 +34,12 @@ data class CodexUsageExport(
3334
usedTokens = lastTokenUsage.totalTokens,
3435
limitTokens = contextWindowTokens,
3536
),
36-
fiveHourWindow = primaryWindow.toQuotaWindow(fallbackLabel = "5h"),
37-
sevenDayWindow = secondaryWindow.toQuotaWindow(fallbackLabel = "7d"),
37+
fiveHourWindow = rateWindows
38+
.firstOrNull { it.windowMinutes == 300 }
39+
.toQuotaWindow(fallbackLabel = "5h"),
40+
sevenDayWindow = rateWindows
41+
.firstOrNull { it.windowMinutes == 10_080 }
42+
.toQuotaWindow(fallbackLabel = "7d"),
3843
tokensToday = tokensToday,
3944
trend = TrendSeries(values = trendPercent),
4045
)

apps/android-wear/src/main/java/com/yummbj/codex/usage/wear/ui/CodexUsageWearApp.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import androidx.compose.ui.text.font.FontWeight
2828
import androidx.compose.ui.unit.dp
2929
import androidx.compose.ui.unit.sp
3030
import androidx.wear.compose.material3.MaterialTheme
31-
import com.yummbj.codex.usage.wear.model.MockUsageSnapshots
3231
import com.yummbj.codex.usage.wear.model.UsageSnapshot
3332
import kotlinx.coroutines.delay
3433
import kotlinx.coroutines.launch
@@ -44,12 +43,12 @@ fun CodexUsageWearApp(
4443
onRefreshSnapshot: suspend () -> UsageSnapshot? = { null },
4544
) {
4645
var snapshot by remember(codexUsageSnapshot) {
47-
mutableStateOf(codexUsageSnapshot ?: MockUsageSnapshots.running)
46+
mutableStateOf<UsageSnapshot?>(codexUsageSnapshot)
4847
}
4948
var isEditingServiceHost by remember { mutableStateOf(false) }
5049
val scope = rememberCoroutineScope()
5150

52-
LaunchedEffect(Unit) {
51+
LaunchedEffect(serviceHost) {
5352
while (true) {
5453
val refreshed = onRefreshSnapshot()
5554
if (refreshed != null) {

apps/android-wear/src/main/java/com/yummbj/codex/usage/wear/ui/UsageDashboard.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import kotlin.math.roundToInt
5454
@OptIn(ExperimentalFoundationApi::class)
5555
@Composable
5656
fun UsageDashboard(
57-
snapshot: UsageSnapshot,
57+
snapshot: UsageSnapshot?,
5858
onRefreshSnapshot: () -> Unit,
5959
onEditServiceHost: () -> Unit = {},
6060
modifier: Modifier = Modifier,
@@ -68,13 +68,37 @@ fun UsageDashboard(
6868
onLongClick = onEditServiceHost,
6969
)
7070
.semantics {
71-
contentDescription = "Codex usage ${snapshot.context.displayText}, ${snapshot.statusLabel}"
71+
contentDescription = snapshot?.let {
72+
"Codex usage ${it.context.displayText}, ${it.statusLabel}"
73+
} ?: "Codex usage unavailable"
7274
},
7375
contentAlignment = Alignment.Center,
7476
) {
7577
val scale = min(maxWidth.value / 208f, maxHeight.value / 244f).coerceAtMost(1.18f)
7678
val horizontalPadding = 8.dp * scale
7779

80+
if (snapshot == null) {
81+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
82+
BasicText(
83+
text = "No data",
84+
style = TextStyle(
85+
color = Color.White,
86+
fontSize = (16 * scale).sp,
87+
fontWeight = FontWeight.SemiBold,
88+
),
89+
)
90+
Spacer(Modifier.height(4.dp * scale))
91+
BasicText(
92+
text = "Tap to retry",
93+
style = TextStyle(
94+
color = WatchColors.secondaryText,
95+
fontSize = (10 * scale).sp,
96+
),
97+
)
98+
}
99+
return@BoxWithConstraints
100+
}
101+
78102
// 主屏从上到下依次显示:上下文用量弧线、三项摘要指标、趋势柱状图、5h/7d 配额环。
79103
Column(
80104
modifier = Modifier
53 KB
Loading
17.4 KB
Loading

apps/android-wear/src/main/res/drawable/ic_launcher_foreground.xml

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3+
<background android:drawable="@drawable/ic_launcher_background" />
4+
<foreground android:drawable="@drawable/ic_launcher_foreground" />
5+
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
6+
</adaptive-icon>

apps/android-wear/src/test/java/com/yummbj/codex/usage/wear/model/UsageSnapshotTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,39 @@ class UsageSnapshotTest {
111111
assertEquals("320.4K", snapshot.tokensTodayDisplay)
112112
assertEquals(0.12f, snapshot.trend.normalizedValues.first())
113113
}
114+
115+
@Test
116+
fun singleSevenDayPrimaryWindowMapsToSevenDayRing() {
117+
val export = CodexUsageExport(
118+
generatedAtEpochSeconds = 1_784_801_998,
119+
lastActivityEpochSeconds = 1_784_801_995,
120+
limitId = "codex",
121+
currentModel = "gpt-5.6-sol",
122+
planType = "prolite",
123+
contextWindowTokens = 258_400,
124+
lastTokenUsage = CodexTokenUsage(
125+
inputTokens = 44_248,
126+
cachedInputTokens = 42_752,
127+
outputTokens = 565,
128+
reasoningOutputTokens = 208,
129+
totalTokens = 44_813,
130+
),
131+
tokensToday = 9_904_228,
132+
primaryWindow = CodexRateWindow(
133+
usedPercent = 22.0,
134+
windowMinutes = 10_080,
135+
resetsAtEpochSeconds = 1_785_286_832,
136+
),
137+
secondaryWindow = null,
138+
trendPercent = emptyList(),
139+
)
140+
141+
val snapshot = export.toUsageSnapshot(nowEpochSeconds = 1_784_801_998)
142+
143+
assertEquals("5h", snapshot.fiveHourWindow.label)
144+
assertEquals(0.0, snapshot.fiveHourWindow.percent, 0.0)
145+
assertEquals("--", snapshot.fiveHourWindow.resetDisplay)
146+
assertEquals("7d", snapshot.sevenDayWindow.label)
147+
assertEquals(22.0, snapshot.sevenDayWindow.percent, 0.0)
148+
}
114149
}

0 commit comments

Comments
 (0)