v1.2: refresh icon rotates while fetching (12fps via partial RemoteViews updates)

This commit is contained in:
2026-05-22 15:49:13 +00:00
parent adb389984e
commit fafa5a3bb7
3 changed files with 43 additions and 11 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "me.khodak.claudeusage"
minSdk = 26
targetSdk = 34
versionCode = 2
versionName = "1.1"
versionCode = 3
versionName = "1.2"
}
signingConfigs {
@@ -149,6 +149,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
if (status.isNotBlank()) status else if (updatedMs > 0) formatTime(updatedMs) else "")
v.setInt(R.id.btn_refresh, "setColorFilter",
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
v.setFloat(R.id.btn_refresh, "setRotation", 0f)
return v
}
@@ -230,6 +231,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
)
v.setInt(R.id.btn_refresh, "setColorFilter",
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
v.setFloat(R.id.btn_refresh, "setRotation", 0f)
return v
}
@@ -6,8 +6,14 @@ import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.SystemClock
import android.util.SizeF
import android.widget.RemoteViews
import androidx.work.*
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import me.khodak.claudeusage.data.PreferencesManager
import java.util.concurrent.TimeUnit
@@ -22,19 +28,44 @@ class UsageUpdateWorker(
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
coroutineScope {
val animJob = launch { rotateRefreshIcon() }
try {
val data = UsageRepository(prefs).fetchUsage()
prefs.saveUsageData(data)
} catch (_: Exception) {}
animJob.cancel()
}
// Always push widget update using latest prefs + cached api data
pushWidgetUpdate()
return Result.success()
}
private suspend fun rotateRefreshIcon() {
val manager = AppWidgetManager.getInstance(context)
val ids = manager.getAppWidgetIds(ComponentName(context, ClaudeUsageWidget::class.java))
var angle = 0f
while (true) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val small = RemoteViews(context.packageName, R.layout.widget_layout_small)
small.setFloat(R.id.btn_refresh, "setRotation", angle)
val large = RemoteViews(context.packageName, R.layout.widget_layout)
large.setFloat(R.id.btn_refresh, "setRotation", angle)
val rv = RemoteViews(mapOf(
SizeF(50f, 50f) to small,
SizeF(50f, 120f) to large
))
ids.forEach { id -> manager.partiallyUpdateAppWidget(id, rv) }
} else {
val rv = RemoteViews(context.packageName, R.layout.widget_layout)
rv.setFloat(R.id.btn_refresh, "setRotation", angle)
ids.forEach { id -> manager.partiallyUpdateAppWidget(id, rv) }
}
angle = (angle + 30f) % 360f
delay(83) // ~12 fps, one full rotation per second
}
}
private fun pushWidgetUpdate() {
ClaudeUsageWidget.isRefreshing = false
val manager = AppWidgetManager.getInstance(context)
@@ -57,8 +88,7 @@ class UsageUpdateWorker(
}
fun cancelPeriodicRefresh(context: Context) =
(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager)
.cancel(alarmIntent(context))
(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) .cancel(alarmIntent(context))
fun triggerImmediateRefresh(context: Context) {
WorkManager.getInstance(context).enqueueUniqueWork(