Initial release: Claude Pro usage widget for Android

This commit is contained in:
2026-05-22 15:11:56 +00:00
commit 33ac02ead4
639 changed files with 52708 additions and 0 deletions
@@ -0,0 +1,77 @@
package me.khodak.claudeusage
import android.app.AlarmManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.SystemClock
import androidx.work.*
import me.khodak.claudeusage.data.PreferencesManager
import java.util.concurrent.TimeUnit
class UsageUpdateWorker(
private val context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val prefs = PreferencesManager(context)
if (!prefs.isLoggedIn()) return Result.success()
prefs.markTodayActive()
// Try API — save result (even on failure, local data is preserved)
try {
val data = UsageRepository(prefs).fetchUsage()
prefs.saveUsageData(data)
} catch (_: Exception) {
// API failed — don't save, keep existing cached data
}
// Always push widget update using latest prefs + cached api data
pushWidgetUpdate()
return Result.success()
}
private fun pushWidgetUpdate() {
ClaudeUsageWidget.isRefreshing = false
val manager = AppWidgetManager.getInstance(context)
val ids = manager.getAppWidgetIds(ComponentName(context, ClaudeUsageWidget::class.java))
ids.forEach { id -> ClaudeUsageWidget.updateWidget(context, manager, id) }
}
companion object {
private const val WORK_ONE_SHOT = "claude_oneshot"
private const val ALARM_CODE = 1001
private const val INTERVAL_MS = 5 * 60 * 1000L
fun schedulePeriodicRefresh(context: Context) {
val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
am.setAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + INTERVAL_MS,
alarmIntent(context)
)
}
fun cancelPeriodicRefresh(context: Context) =
(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager)
.cancel(alarmIntent(context))
fun triggerImmediateRefresh(context: Context) {
WorkManager.getInstance(context).enqueueUniqueWork(
WORK_ONE_SHOT,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<UsageUpdateWorker>().build()
)
}
private fun alarmIntent(context: Context) = PendingIntent.getBroadcast(
context, ALARM_CODE,
Intent(context, AlarmReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
}