1 Commits

Author SHA1 Message Date
amir 6801a60183 bump versionCode to 10 / versionName to 1.9, enable BuildConfig generation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 03:30:59 +00:00
17 changed files with 89 additions and 473 deletions
-9
View File
@@ -1,7 +1,3 @@
<p align="center">
<img src="icon.png" width="108" alt="Claude Usage Widget">
</p>
# Claude Usage Widget
Android home screen widget that shows your Claude Pro usage at a glance.
@@ -10,11 +6,6 @@ Android home screen widget that shows your Claude Pro usage at a glance.
- **SESSION** bar — current 5-hour window utilization with reset time
- **WEEKLY** bar — 7-day rolling usage with reset time
- **Pace marker** — a colored tick on each bar shows where you *should be* right now to finish at
exactly 100% by reset. Tick color grades your projection: green (way under budget) → teal →
yellow → orange → red → purple (burning way too fast), with an "X% over/under pace" label.
- **Peak-hours indicator** — a Claude burst icon that lights up 🔥 during Anthropic's peak window
(511 AM Pacific, MonFri), when tokens burn faster, with a countdown to the window close.
- Tap the widget to open the app; tap ⟳ to force-refresh
- Responsive: works as 4×1 (compact) or 4×2 (full)
- Auto-refreshes every 5 minutes in the background
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "me.khodak.claudeusage"
minSdk = 26
targetSdk = 34
versionCode = 14
versionName = "1.13"
versionCode = 10
versionName = "1.9"
}
signingConfigs {
-1
View File
@@ -11,7 +11,6 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ClaudeUsage"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false">
<activity
@@ -1,59 +0,0 @@
package me.khodak.claudeusage
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
/**
* Draws a usage bar as a Bitmap: rounded track + rounded fill + an optional vertical pace tick.
* Used both in the home-screen widget (setImageViewBitmap) and the in-app card, so the two render
* identically. Bitmaps are rendered at a fixed width and stretched horizontally via ImageView
* scaleType=fitXY; height is fixed so there is no vertical distortion.
*/
object BarRenderer {
private const val TRACK_COLOR = 0xFF252525.toInt()
fun render(
usedPct: Int,
markerPct: Int?,
fillColor: Int,
markerColor: Int?,
wPx: Int = 500,
hPx: Int = 14,
cornerPx: Float = 7f
): Bitmap {
val bmp = Bitmap.createBitmap(wPx, hPx, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
// Track
paint.color = TRACK_COLOR
val full = RectF(0f, 0f, wPx.toFloat(), hPx.toFloat())
canvas.drawRoundRect(full, cornerPx, cornerPx, paint)
// Fill
val pct = usedPct.coerceIn(0, 100)
if (pct > 0) {
paint.color = fillColor
val fillW = (wPx * pct / 100f).coerceAtLeast(cornerPx * 2)
val fill = RectF(0f, 0f, fillW, hPx.toFloat())
canvas.drawRoundRect(fill, cornerPx, cornerPx, paint)
}
// 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 tickW = (wPx * 0.016f).coerceIn(6f, 10f)
var x = wPx * m / 100f
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
}
}
@@ -87,15 +87,14 @@ class ClaudeUsageWidget : AppWidgetProvider() {
private fun buildSmallViews(context: Context, prefs: PreferencesManager, apiData: UsageData?): RemoteViews {
val v = RemoteViews(context.packageName, R.layout.widget_layout_small)
applyPeak(v, showText = false)
if (!prefs.isLoggedIn()) {
v.setTextViewText(R.id.tv_session_value, "")
v.setTextViewText(R.id.tv_session_label, "Not signed in")
v.setTextViewText(R.id.tv_weekly_value, "")
v.setTextViewText(R.id.tv_weekly_label, "")
v.setTextViewText(R.id.tv_status, "")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, 0, false)
v.setProgressBar(R.id.progress_bar_weekly, 100, 0, false)
return v
}
val hasUtilization = apiData != null && apiData.fiveHourUtilization >= 0f
@@ -105,14 +104,14 @@ class ClaudeUsageWidget : AppWidgetProvider() {
hasUtilization -> {
val pct = apiData!!.fiveHourUtilization.toInt()
v.setTextViewText(R.id.tv_session_value, "$pct%")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, pct, false)
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
}
hasApiMessages -> {
val rem = apiData!!.effectiveRemaining
val lim = apiData.messagesLimit
v.setTextViewText(R.id.tv_session_value, "$rem/$lim")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(apiData.progressPercent, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, apiData.progressPercent, false)
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.resetAtEpoch))
}
sessionStart > 0 -> {
@@ -120,26 +119,25 @@ class ClaudeUsageWidget : AppWidgetProvider() {
val h = TimeUnit.MILLISECONDS.toHours(elapsedMs)
val m = TimeUnit.MILLISECONDS.toMinutes(elapsedMs) % 60
v.setTextViewText(R.id.tv_session_value, if (h > 0) "${h}h${m}m" else "${m}m")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, 0, false)
v.setTextViewText(R.id.tv_session_label, "active")
}
else -> {
v.setTextViewText(R.id.tv_session_value, "")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, 0, false)
v.setTextViewText(R.id.tv_session_label, "")
}
}
val hasWeekly = apiData != null && apiData.weeklyUtilization >= 0f
if (hasWeekly) {
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, if (pace != null) MARKER_COLOR else null))
v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
v.setProgressBar(R.id.progress_bar_weekly, 100, wPct, false)
v.setTextViewText(R.id.tv_weekly_label, formatReset(apiData.weeklyResetAtEpoch))
} else {
val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
v.setTextViewText(R.id.tv_weekly_value, "${weeklyDays}d")
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
v.setProgressBar(R.id.progress_bar_weekly, 100, 0, false)
v.setTextViewText(R.id.tv_weekly_label, "")
}
val resetEpoch = apiData?.effectiveResetEpoch ?: -1L
@@ -159,7 +157,6 @@ class ClaudeUsageWidget : AppWidgetProvider() {
private fun buildViews(context: Context, prefs: PreferencesManager, apiData: UsageData?): RemoteViews {
val v = RemoteViews(context.packageName, R.layout.widget_layout)
applyPeak(v, showText = true)
// ── Not logged in ────────────────────────────────────────────────
if (!prefs.isLoggedIn()) {
@@ -168,8 +165,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
v.setTextViewText(R.id.tv_weekly_value, "")
v.setTextViewText(R.id.tv_weekly_label, "Open app to sign in")
v.setTextViewText(R.id.tv_status, "")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, 0, false)
return v
}
@@ -182,14 +178,14 @@ class ClaudeUsageWidget : AppWidgetProvider() {
hasUtilization -> {
val pct = apiData!!.fiveHourUtilization.toInt()
v.setTextViewText(R.id.tv_session_value, "$pct%")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, pct, false)
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
}
hasApiMessages -> {
val rem = apiData!!.effectiveRemaining
val lim = apiData.messagesLimit
v.setTextViewText(R.id.tv_session_value, "$rem / $lim")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(apiData.progressPercent, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, apiData.progressPercent, false)
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.resetAtEpoch))
}
sessionStart > 0 -> {
@@ -198,12 +194,12 @@ class ClaudeUsageWidget : AppWidgetProvider() {
val m = TimeUnit.MILLISECONDS.toMinutes(elapsedMs) % 60
val display = if (h > 0) "${h}h ${m}m" else if (m > 0) "${m}m" else "Just started"
v.setTextViewText(R.id.tv_session_value, display)
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, 0, false)
v.setTextViewText(R.id.tv_session_label, "session active")
}
else -> {
v.setTextViewText(R.id.tv_session_value, "")
v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
v.setProgressBar(R.id.progress_bar, 100, 0, false)
v.setTextViewText(R.id.tv_session_label, "")
}
}
@@ -212,14 +208,13 @@ class ClaudeUsageWidget : AppWidgetProvider() {
val hasWeekly = apiData != null && apiData.weeklyUtilization >= 0f
if (hasWeekly) {
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, if (pace != null) MARKER_COLOR else null))
v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
v.setProgressBar(R.id.progress_bar_weekly, 100, wPct, false)
v.setTextViewText(R.id.tv_weekly_label, formatReset(apiData.weeklyResetAtEpoch))
} else {
val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
v.setTextViewText(R.id.tv_weekly_value, "$weeklyDays d")
v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
v.setProgressBar(R.id.progress_bar_weekly, 100, 0, false)
v.setTextViewText(R.id.tv_weekly_label, "active this week")
}
@@ -243,20 +238,6 @@ class ClaudeUsageWidget : AppWidgetProvider() {
return v
}
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) {
val peak = PeakHours.isPeak()
v.setInt(R.id.img_peak, "setColorFilter",
if (peak) 0xFFCC785C.toInt() else 0xFF666666.toInt())
if (showText) {
v.setTextViewText(R.id.tv_peak, if (peak) "🔥 PEAK" else "")
}
}
private fun formatReset(epochMs: Long): String {
if (epochMs <= 0) return ""
val now = System.currentTimeMillis()
@@ -270,15 +251,6 @@ 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))
}
@@ -72,8 +72,8 @@ class LoginActivity : AppCompatActivity() {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = false
javaScriptCanOpenWindowsAutomatically = false
databaseEnabled = true
javaScriptCanOpenWindowsAutomatically = true
setSupportMultipleWindows(false)
// Standard Android Chrome UA — less suspicious than desktop
userAgentString = "Mozilla/5.0 (Linux; Android 13; Pixel 7) " +
@@ -104,37 +104,17 @@ class MainActivity : AppCompatActivity() {
if (!loggedIn || data == null) return
// ── Peak-hours row ───────────────────────────────────────────────────
val peak = PeakHours.state()
binding.imgPeak.setColorFilter(if (peak.active) PEAK_ON else PEAK_OFF)
binding.tvPeak.setTextColor(if (peak.active) PEAK_ON else 0xFFAAAAAA.toInt())
binding.tvPeak.text = if (peak.active)
"🔥 Peak hours — ${peak.endsInLabel} · ${peak.windowLabel}"
else
"Off-peak · ${peak.windowLabel}"
binding.progressBar.progress = data.progressPercent
// ── Session (5-hour) bar — no pace marker ────────────────────────────
binding.barSession.setImageBitmap(
BarRenderer.render(data.progressPercent, null, SESSION_FILL, null)
)
// ── 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, if (weeklyPace != null) MARKER_COLOR else null)
)
binding.progressBarWeekly.progress = wPct
binding.tvWeeklyUsage.text = "$wPct% this week"
} else {
binding.barWeekly.setImageBitmap(BarRenderer.render(0, null, WEEKLY_FILL, null))
binding.progressBarWeekly.progress = 0
binding.tvWeeklyUsage.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()
@@ -150,7 +130,7 @@ class MainActivity : AppCompatActivity() {
}
binding.tvReset.text = formatReset(data.effectiveResetEpoch)
binding.tvWeeklyReset.text = formatResetDay(data.weeklyResetAtEpoch)
binding.tvWeeklyReset.text = formatReset(data.weeklyResetAtEpoch)
binding.tvUpdated.text = if (data.lastUpdated > 0)
"Last updated: ${SimpleDateFormat("h:mm a, MMM d", Locale.US).format(Date(data.lastUpdated))}"
else ""
@@ -171,21 +151,4 @@ class MainActivity : AppCompatActivity() {
else -> "Resets ${SimpleDateFormat("EEE", Locale.US).format(Date(epochMs))} $timeStr"
}
}
/** 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()
}
}
@@ -1,65 +0,0 @@
package me.khodak.claudeusage
/**
* Pace projection mirroring hamed-elfayome/Claude-Usage-Tracker's PaceStatus.
*
* Idea: if you keep your current burn rate, where do you land at reset?
* projected = (usedPct / 100) / elapsedFraction
* The "where you should be right now" marker sits at elapsedFraction*100 on the bar —
* i.e. the position that finishes at exactly 100% by reset.
*/
object PaceCalc {
const val SESSION_WINDOW_MS = 5L * 60 * 60 * 1000 // 5 hours
const val WEEKLY_WINDOW_MS = 7L * 24 * 60 * 60 * 1000 // 7 days
// System-style tier colors (ARGB).
private const val GREEN = 0xFF34C759.toInt()
private const val TEAL = 0xFF30B0C7.toInt()
private const val YELLOW = 0xFFFFCC00.toInt()
private const val ORANGE = 0xFFFF9500.toInt()
private const val RED = 0xFFFF3B30.toInt()
private const val PURPLE = 0xFFAF52DE.toInt()
data class Pace(
val markerPct: Int, // where you "should be" now (0..100)
val projected: Float, // projected end-of-period fraction (1.0 == exactly 100%)
val tierColor: Int, // ARGB color for the tier
val label: String // short interpretation, e.g. "sustainable pace"
)
/**
* @param usedPct current utilization 0..100
* @param resetEpoch epoch millis when this window resets (period end)
* @param windowMs total length of the window
* @return Pace, or null if we can't meaningfully project yet
*/
fun compute(
usedPct: Float,
resetEpoch: Long,
windowMs: Long,
now: Long = System.currentTimeMillis()
): Pace? {
if (resetEpoch <= 0L || usedPct < 0f) return null
val start = resetEpoch - windowMs
val elapsed = now - start
val elapsedFraction = elapsed.toFloat() / windowMs.toFloat()
// Match reference: need >=3% elapsed and period not complete.
if (elapsedFraction < 0.03f || elapsedFraction >= 1.0f) return null
val markerPct = (elapsedFraction * 100f).toInt().coerceIn(0, 100)
if (usedPct == 0f) {
return Pace(markerPct, 0f, GREEN, "way under budget")
}
val projected = (usedPct / 100f) / elapsedFraction
val (color, label) = when {
projected < 0.50f -> GREEN to "way under budget"
projected < 0.75f -> TEAL to "sustainable pace"
projected < 0.90f -> YELLOW to "starting to push it"
projected < 1.00f -> ORANGE to "will likely hit limit"
projected < 1.20f -> RED to "on track to exceed"
else -> PURPLE to "burning way too fast"
}
return Pace(markerPct, projected, color, label)
}
}
@@ -1,80 +0,0 @@
package me.khodak.claudeusage
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
/**
* Anthropic peak usage window, mirroring hamed-elfayome/Claude-Usage-Tracker's PeakHoursService:
* 5:0011:00 AM America/Los_Angeles, MondayFriday. Tokens burn faster during this window.
*/
object PeakHours {
private const val PEAK_START_HOUR = 5 // 5 AM PT inclusive
private const val PEAK_END_HOUR = 11 // 11 AM PT exclusive
private val PT: TimeZone = TimeZone.getTimeZone("America/Los_Angeles")
data class PeakState(
val active: Boolean,
val endsInLabel: String, // e.g. "ends in 2h 14m" (only meaningful when active)
val windowLabel: String // e.g. "511 AM PT · 8 AM2 PM your time"
)
fun isPeak(now: Long = System.currentTimeMillis()): Boolean {
val cal = GregorianCalendar(PT).apply { timeInMillis = now }
val dow = cal.get(Calendar.DAY_OF_WEEK) // Sun=1 .. Sat=7
val weekday = dow in Calendar.MONDAY..Calendar.FRIDAY
val hour = cal.get(Calendar.HOUR_OF_DAY)
return weekday && hour in PEAK_START_HOUR until PEAK_END_HOUR
}
/** Epoch millis of 11:00 AM PT on the current PT day (peak window close). */
fun peakEndEpoch(now: Long = System.currentTimeMillis()): Long {
val cal = GregorianCalendar(PT).apply {
timeInMillis = now
set(Calendar.HOUR_OF_DAY, PEAK_END_HOUR)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
return cal.timeInMillis
}
fun state(now: Long = System.currentTimeMillis()): PeakState {
val active = isPeak(now)
val endsIn = if (active) {
val rem = peakEndEpoch(now) - now
val h = TimeUnit.MILLISECONDS.toHours(rem)
val m = TimeUnit.MILLISECONDS.toMinutes(rem) % 60
if (h > 0) "ends in ${h}h ${m}m" else "ends in ${m}m"
} else ""
return PeakState(active, endsIn, localWindowString(now))
}
/** "511 AM PT · 8 AM2 PM your time" — peak window translated to the device timezone. */
fun localWindowString(now: Long = System.currentTimeMillis()): String {
val local = TimeZone.getDefault()
if (local.id == PT.id) return "511 AM PT"
val startLocal = ptHourToLocal(PEAK_START_HOUR, now, local)
val endLocal = ptHourToLocal(PEAK_END_HOUR, now, local)
val fmt = SimpleDateFormat("h a", Locale.US).apply { timeZone = local }
val s = fmt.format(Date(startLocal)).replace(" ", "")
val e = fmt.format(Date(endLocal)).replace(" ", "")
return "511 AM PT · $s$e your time"
}
private fun ptHourToLocal(ptHour: Int, now: Long, local: TimeZone): Long {
val cal = GregorianCalendar(PT).apply {
timeInMillis = now
set(Calendar.HOUR_OF_DAY, ptHour)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
return cal.timeInMillis // absolute instant; formatting in `local` renders local wall time
}
}
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Claude "sunburst" mark. Drawn in white; tinted at runtime via setColorFilter
(calm #666666, peak #CC785C). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:strokeColor="#FFFFFF"
android:strokeWidth="1.7"
android:strokeLineCap="round"
android:pathData="
M12,3 L12,7
M12,17 L12,21
M3,12 L7,12
M17,12 L21,12
M5.6,5.6 L8.4,8.4
M15.6,15.6 L18.4,18.4
M18.4,5.6 L15.6,8.4
M8.4,15.6 L5.6,18.4
M12,4.5 L12,6.2
M12,17.8 L12,19.5
M4.5,12 L6.2,12
M17.8,12 L19.5,12" />
<path
android:fillColor="#FFFFFF"
android:pathData="M12,9.6 a2.4,2.4 0 1,0 0.01,0 z" />
</vector>
+16 -59
View File
@@ -92,33 +92,6 @@
android:background="@drawable/widget_background"
android:padding="20dp">
<!-- Peak-hours row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="14dp">
<ImageView
android:id="@+id/imgPeak"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_claude_burst"
android:contentDescription="Peak hours" />
<TextView
android:id="@+id/tvPeak"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=""
android:textColor="#AAAAAA"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -137,24 +110,16 @@
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/barSession"
<ProgressBar
android:id="@+id/progressBar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="9dp"
android:layout_height="8dp"
android:layout_marginTop="12dp"
android:scaleType="fitXY"
android:adjustViewBounds="false"
android:contentDescription="Session usage bar" />
<TextView
android:id="@+id/tvSessionPace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text=""
android:textColor="#AAAAAA"
android:textSize="13sp"
android:textStyle="bold" />
android:max="100"
android:progress="0"
android:progressTint="#CC785C"
android:progressBackgroundTint="#3A3A3A" />
<TextView
android:id="@+id/tvReset"
@@ -183,24 +148,16 @@
android:textSize="16sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/barWeekly"
<ProgressBar
android:id="@+id/progressBarWeekly"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="9dp"
android:layout_height="8dp"
android:layout_marginTop="8dp"
android:scaleType="fitXY"
android:adjustViewBounds="false"
android:contentDescription="Weekly usage bar" />
<TextView
android:id="@+id/tvWeeklyPace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text=""
android:textColor="#AAAAAA"
android:textSize="13sp"
android:textStyle="bold" />
android:max="100"
android:progress="0"
android:progressTint="#7B8FCC"
android:progressBackgroundTint="#3A3A3A" />
<TextView
android:id="@+id/tvWeeklyReset"
+25 -43
View File
@@ -23,24 +23,6 @@
android:textSize="13sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/img_peak"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_claude_burst"
android:contentDescription="Peak hours" />
<TextView
android:id="@+id/tv_peak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text=""
android:textColor="#CC785C"
android:textSize="9sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_refresh"
android:layout_width="32dp"
@@ -72,7 +54,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="SESSION"
android:textColor="#FFFFFF"
android:textColor="#555555"
android:textSize="9sp"
android:textStyle="bold" />
@@ -87,14 +69,16 @@
</LinearLayout>
<ImageView
android:id="@+id/bar_session"
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="6dp"
android:layout_height="5dp"
android:layout_marginTop="5dp"
android:scaleType="fitXY"
android:adjustViewBounds="false"
android:contentDescription="Session usage bar" />
android:max="100"
android:progress="0"
android:progressTint="#CC785C"
android:progressBackgroundTint="#252525" />
<TextView
android:id="@+id/tv_session_label"
@@ -102,9 +86,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text=""
android:textColor="#FFFFFF"
android:textSize="11sp"
android:textStyle="bold" />
android:textColor="#666666"
android:textSize="11sp" />
<!-- 7-day window bar -->
<LinearLayout
@@ -119,7 +102,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="WEEKLY"
android:textColor="#FFFFFF"
android:textColor="#555555"
android:textSize="9sp"
android:textStyle="bold" />
@@ -134,14 +117,16 @@
</LinearLayout>
<ImageView
android:id="@+id/bar_weekly"
<ProgressBar
android:id="@+id/progress_bar_weekly"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="6dp"
android:layout_height="5dp"
android:layout_marginTop="5dp"
android:scaleType="fitXY"
android:adjustViewBounds="false"
android:contentDescription="Weekly usage bar" />
android:max="100"
android:progress="0"
android:progressTint="#7B8FCC"
android:progressBackgroundTint="#252525" />
<TextView
android:id="@+id/tv_weekly_label"
@@ -149,9 +134,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text=""
android:textColor="#FFFFFF"
android:textSize="11sp"
android:textStyle="bold" />
android:textColor="#666666"
android:textSize="11sp" />
<!-- Footer -->
<LinearLayout
@@ -167,9 +151,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=""
android:textColor="#FFFFFF"
android:textColor="#CC785C"
android:textSize="9sp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="end" />
@@ -178,9 +161,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#FFFFFF"
android:textSize="9sp"
android:textStyle="bold" />
android:textColor="#444444"
android:textSize="9sp" />
</LinearLayout>
+23 -30
View File
@@ -23,14 +23,6 @@
android:textSize="11sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/img_peak"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginEnd="6dp"
android:src="@drawable/ic_claude_burst"
android:contentDescription="Peak hours" />
<ImageButton
android:id="@+id/btn_refresh"
android:layout_width="28dp"
@@ -54,7 +46,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="SESSION"
android:textColor="#FFFFFF"
android:textColor="#555555"
android:textSize="8sp"
android:textStyle="bold" />
@@ -73,20 +65,21 @@
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text=""
android:textColor="#FFFFFF"
android:textSize="10sp"
android:textStyle="bold" />
android:textColor="#555555"
android:textSize="10sp" />
</LinearLayout>
<ImageView
android:id="@+id/bar_session"
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_height="4dp"
android:layout_marginTop="3dp"
android:scaleType="fitXY"
android:adjustViewBounds="false"
android:contentDescription="Session usage bar" />
android:max="100"
android:progress="0"
android:progressTint="#CC785C"
android:progressBackgroundTint="#252525" />
<!-- 7-DAY row -->
<LinearLayout
@@ -101,7 +94,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="WEEKLY"
android:textColor="#FFFFFF"
android:textColor="#555555"
android:textSize="8sp"
android:textStyle="bold" />
@@ -120,20 +113,21 @@
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text=""
android:textColor="#FFFFFF"
android:textSize="10sp"
android:textStyle="bold" />
android:textColor="#555555"
android:textSize="10sp" />
</LinearLayout>
<ImageView
android:id="@+id/bar_weekly"
<ProgressBar
android:id="@+id/progress_bar_weekly"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_height="4dp"
android:layout_marginTop="3dp"
android:scaleType="fitXY"
android:adjustViewBounds="false"
android:contentDescription="Weekly usage bar" />
android:max="100"
android:progress="0"
android:progressTint="#7B8FCC"
android:progressBackgroundTint="#252525" />
<TextView
android:id="@+id/tv_status"
@@ -141,9 +135,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text=""
android:textColor="#FFFFFF"
android:textColor="#CC785C"
android:textSize="8sp"
android:textStyle="bold"
android:maxLines="1" />
</LinearLayout>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.
Binary file not shown.