v1.13: drop session marker, single-color weekly marker, weekday reset
- Remove the pace marker from the 5-hour (session) bar entirely.
- Weekly bar marker is now a single color (white), no green→purple tiers.
- Marker is a clean rounded tick; removed the white-halo/tier styling.
- Remove the '% over/under pace' text everywhere (widget + app).
- Weekly reset label now shows the weekday ('Resets Friday 3:00 PM'),
never 'tomorrow'.
versionCode 14 / versionName 1.13. Includes rebuilt signed release APK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "me.khodak.claudeusage"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 13
|
||||
versionName = "1.12"
|
||||
versionCode = 14
|
||||
versionName = "1.13"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -19,7 +19,7 @@ object BarRenderer {
|
||||
usedPct: Int,
|
||||
markerPct: Int?,
|
||||
fillColor: Int,
|
||||
tierColor: Int?,
|
||||
markerColor: Int?,
|
||||
wPx: Int = 500,
|
||||
hPx: Int = 14,
|
||||
cornerPx: Float = 7f
|
||||
@@ -42,20 +42,16 @@ object BarRenderer {
|
||||
canvas.drawRoundRect(fill, cornerPx, cornerPx, paint)
|
||||
}
|
||||
|
||||
// Pace tick — "where you should be right now".
|
||||
// Wider than before and wrapped in a white halo so it stands out on any fill color.
|
||||
if (markerPct != null && tierColor != null) {
|
||||
// Pace marker — a single clean tick showing "where you should be right now".
|
||||
// One color (no tiers); rounded ends to match the bar.
|
||||
if (markerPct != null && markerColor != null) {
|
||||
val m = markerPct.coerceIn(0, 100)
|
||||
val coreW = (wPx * 0.022f).coerceIn(9f, 14f) // tier-colored core
|
||||
val halo = coreW * 0.6f // white outline on each side
|
||||
val tickW = (wPx * 0.016f).coerceIn(6f, 10f)
|
||||
var x = wPx * m / 100f
|
||||
x = x.coerceIn(coreW / 2f + halo, wPx - coreW / 2f - halo)
|
||||
// white halo
|
||||
paint.color = 0xFFFFFFFF.toInt()
|
||||
canvas.drawRect(x - coreW / 2f - halo, 0f, x + coreW / 2f + halo, hPx.toFloat(), paint)
|
||||
// tier-colored core
|
||||
paint.color = tierColor
|
||||
canvas.drawRect(x - coreW / 2f, 0f, x + coreW / 2f, hPx.toFloat(), paint)
|
||||
x = x.coerceIn(tickW / 2f, wPx - tickW / 2f)
|
||||
paint.color = markerColor
|
||||
val tick = RectF(x - tickW / 2f, 0f, x + tickW / 2f, hPx.toFloat())
|
||||
canvas.drawRoundRect(tick, tickW / 2f, tickW / 2f, paint)
|
||||
}
|
||||
|
||||
return bmp
|
||||
|
||||
@@ -104,9 +104,8 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
when {
|
||||
hasUtilization -> {
|
||||
val pct = apiData!!.fiveHourUtilization.toInt()
|
||||
val pace = PaceCalc.compute(apiData.fiveHourUtilization, apiData.utilizationResetAtEpoch, PaceCalc.SESSION_WINDOW_MS)
|
||||
v.setTextViewText(R.id.tv_session_value, "$pct%")
|
||||
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, pace?.markerPct, SESSION_FILL, pace?.tierColor))
|
||||
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, null, SESSION_FILL, null))
|
||||
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
|
||||
}
|
||||
hasApiMessages -> {
|
||||
@@ -135,8 +134,8 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
val wPct = apiData!!.weeklyUtilization.toInt()
|
||||
val pace = PaceCalc.compute(apiData.weeklyUtilization, apiData.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
|
||||
v.setTextViewText(R.id.tv_weekly_value, "$wPct%")
|
||||
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, pace?.tierColor))
|
||||
v.setTextViewText(R.id.tv_weekly_label, formatReset(apiData.weeklyResetAtEpoch))
|
||||
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, if (pace != null) MARKER_COLOR else null))
|
||||
v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
|
||||
} else {
|
||||
val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
|
||||
v.setTextViewText(R.id.tv_weekly_value, "${weeklyDays}d")
|
||||
@@ -182,9 +181,8 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
when {
|
||||
hasUtilization -> {
|
||||
val pct = apiData!!.fiveHourUtilization.toInt()
|
||||
val pace = PaceCalc.compute(apiData.fiveHourUtilization, apiData.utilizationResetAtEpoch, PaceCalc.SESSION_WINDOW_MS)
|
||||
v.setTextViewText(R.id.tv_session_value, "$pct%")
|
||||
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, pace?.markerPct, SESSION_FILL, pace?.tierColor))
|
||||
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, null, SESSION_FILL, null))
|
||||
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
|
||||
}
|
||||
hasApiMessages -> {
|
||||
@@ -216,8 +214,8 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
val wPct = apiData!!.weeklyUtilization.toInt()
|
||||
val pace = PaceCalc.compute(apiData.weeklyUtilization, apiData.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
|
||||
v.setTextViewText(R.id.tv_weekly_value, "$wPct%")
|
||||
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, pace?.tierColor))
|
||||
v.setTextViewText(R.id.tv_weekly_label, formatReset(apiData.weeklyResetAtEpoch))
|
||||
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, if (pace != null) MARKER_COLOR else null))
|
||||
v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
|
||||
} else {
|
||||
val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
|
||||
v.setTextViewText(R.id.tv_weekly_value, "$weeklyDays d")
|
||||
@@ -247,6 +245,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
|
||||
private const val SESSION_FILL = 0xFFCC785C.toInt()
|
||||
private const val WEEKLY_FILL = 0xFF7B8FCC.toInt()
|
||||
private const val MARKER_COLOR = 0xFFFFFFFF.toInt() // single-color pace marker (weekly only)
|
||||
|
||||
/** Tints the header burst icon and (optionally) the PEAK text by current peak state. */
|
||||
private fun applyPeak(v: RemoteViews, showText: Boolean) {
|
||||
@@ -271,6 +270,15 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Weekly reset shown with the weekday name ("Resets Friday 3:00 PM"), never "tomorrow". */
|
||||
private fun formatResetDay(epochMs: Long): String {
|
||||
if (epochMs <= 0) return ""
|
||||
if (epochMs <= System.currentTimeMillis()) return "Resets soon"
|
||||
val day = SimpleDateFormat("EEEE", Locale.US).format(Date(epochMs))
|
||||
val timeStr = SimpleDateFormat("h:mm a", Locale.US).format(Date(epochMs))
|
||||
return "Resets $day $timeStr"
|
||||
}
|
||||
|
||||
private fun formatTime(ms: Long) =
|
||||
SimpleDateFormat("h:mm a", Locale.US).format(Date(ms))
|
||||
}
|
||||
|
||||
@@ -113,31 +113,28 @@ class MainActivity : AppCompatActivity() {
|
||||
else
|
||||
"Off-peak · ${peak.windowLabel}"
|
||||
|
||||
// ── Session (5-hour) bar + pace ──────────────────────────────────────
|
||||
val sessionPct = data.progressPercent
|
||||
val sessionPace = if (data.fiveHourUtilization >= 0f)
|
||||
PaceCalc.compute(data.fiveHourUtilization, data.utilizationResetAtEpoch, PaceCalc.SESSION_WINDOW_MS)
|
||||
else null
|
||||
// ── Session (5-hour) bar — no pace marker ────────────────────────────
|
||||
binding.barSession.setImageBitmap(
|
||||
BarRenderer.render(sessionPct, sessionPace?.markerPct, SESSION_FILL, sessionPace?.tierColor)
|
||||
BarRenderer.render(data.progressPercent, null, SESSION_FILL, null)
|
||||
)
|
||||
binding.tvSessionPace.text = paceSentence(data.fiveHourUtilization, sessionPace)
|
||||
|
||||
// ── Weekly (7-day) bar + pace ────────────────────────────────────────
|
||||
// ── Weekly (7-day) bar — single-color pace marker ────────────────────
|
||||
if (data.weeklyUtilization >= 0f) {
|
||||
val wPct = data.weeklyUtilization.toInt()
|
||||
val weeklyPace = PaceCalc.compute(data.weeklyUtilization, data.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
|
||||
binding.barWeekly.setImageBitmap(
|
||||
BarRenderer.render(wPct, weeklyPace?.markerPct, WEEKLY_FILL, weeklyPace?.tierColor)
|
||||
BarRenderer.render(wPct, weeklyPace?.markerPct, WEEKLY_FILL, if (weeklyPace != null) MARKER_COLOR else null)
|
||||
)
|
||||
binding.tvWeeklyUsage.text = "$wPct% this week"
|
||||
binding.tvWeeklyPace.text = paceSentence(data.weeklyUtilization, weeklyPace)
|
||||
} else {
|
||||
binding.barWeekly.setImageBitmap(BarRenderer.render(0, null, WEEKLY_FILL, null))
|
||||
binding.tvWeeklyUsage.text = "—"
|
||||
binding.tvWeeklyPace.text = ""
|
||||
}
|
||||
|
||||
// Pace text removed per design — bars carry the signal.
|
||||
binding.tvSessionPace.visibility = View.GONE
|
||||
binding.tvWeeklyPace.visibility = View.GONE
|
||||
|
||||
binding.tvUsage.text = when {
|
||||
data.fiveHourUtilization >= 0f -> {
|
||||
val pct = data.fiveHourUtilization.toInt()
|
||||
@@ -153,7 +150,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.tvReset.text = formatReset(data.effectiveResetEpoch)
|
||||
binding.tvWeeklyReset.text = formatReset(data.weeklyResetAtEpoch)
|
||||
binding.tvWeeklyReset.text = formatResetDay(data.weeklyResetAtEpoch)
|
||||
binding.tvUpdated.text = if (data.lastUpdated > 0)
|
||||
"Last updated: ${SimpleDateFormat("h:mm a, MMM d", Locale.US).format(Date(data.lastUpdated))}"
|
||||
else ""
|
||||
@@ -162,12 +159,6 @@ class MainActivity : AppCompatActivity() {
|
||||
binding.tvError.visibility = if (data.errorMessage.isNotBlank()) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
/** "20% over pace · will likely hit limit" — empty when no projection is available yet. */
|
||||
private fun paceSentence(usedPct: Float, pace: PaceCalc.Pace?): String {
|
||||
if (pace == null) return ""
|
||||
return "${PaceCalc.shortTag(usedPct, pace)} · ${pace.label}"
|
||||
}
|
||||
|
||||
private fun formatReset(epochMs: Long): String {
|
||||
if (epochMs <= 0) return ""
|
||||
val now = System.currentTimeMillis()
|
||||
@@ -181,9 +172,19 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Weekly reset shown with the weekday name ("Resets Friday 3:00 PM"), never "tomorrow". */
|
||||
private fun formatResetDay(epochMs: Long): String {
|
||||
if (epochMs <= 0) return ""
|
||||
if (epochMs <= System.currentTimeMillis()) return "Resets soon"
|
||||
val day = SimpleDateFormat("EEEE", Locale.US).format(Date(epochMs))
|
||||
val timeStr = SimpleDateFormat("h:mm a", Locale.US).format(Date(epochMs))
|
||||
return "Resets $day $timeStr"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SESSION_FILL = 0xFFCC785C.toInt()
|
||||
private const val WEEKLY_FILL = 0xFF7B8FCC.toInt()
|
||||
private const val MARKER_COLOR = 0xFFFFFFFF.toInt() // single-color weekly pace marker
|
||||
private const val PEAK_ON = 0xFFCC785C.toInt()
|
||||
private const val PEAK_OFF = 0xFF666666.toInt()
|
||||
}
|
||||
|
||||
@@ -62,14 +62,4 @@ object PaceCalc {
|
||||
}
|
||||
return Pace(markerPct, projected, color, label)
|
||||
}
|
||||
|
||||
/** Short tag for the widget reset line, e.g. "8% under pace" / "20% over pace" / "on pace". */
|
||||
fun shortTag(usedPct: Float, pace: Pace): String {
|
||||
val delta = usedPct.toInt() - pace.markerPct
|
||||
return when {
|
||||
delta >= 2 -> "$delta% over pace"
|
||||
delta <= -2 -> "${-delta}% under pace"
|
||||
else -> "on pace"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user