Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b69dd2b2 | |||
| 8965477cc7 | |||
| f6f7accfa5 | |||
| d86ffabb98 |
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "me.khodak.claudeusage"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 3
|
||||
versionName = "1.2"
|
||||
versionCode = 6
|
||||
versionName = "1.5"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -39,6 +39,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
companion object {
|
||||
const val ACTION_REFRESH = "me.khodak.claudeusage.ACTION_REFRESH"
|
||||
@Volatile internal var isRefreshing = false
|
||||
@Volatile internal var currentRotation = 0f
|
||||
|
||||
fun updateWidget(context: Context, manager: AppWidgetManager, widgetId: Int) {
|
||||
val prefs = PreferencesManager(context)
|
||||
@@ -149,7 +150,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)
|
||||
v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -231,7 +232,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)
|
||||
v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -6,16 +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.NonCancellable
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.khodak.claudeusage.data.PreferencesManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UsageUpdateWorker(
|
||||
private val context: Context,
|
||||
@@ -35,6 +33,7 @@ class UsageUpdateWorker(
|
||||
prefs.saveUsageData(data)
|
||||
} catch (_: Exception) {}
|
||||
animJob.cancel()
|
||||
animJob.join()
|
||||
}
|
||||
|
||||
pushWidgetUpdate()
|
||||
@@ -44,30 +43,33 @@ class UsageUpdateWorker(
|
||||
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) }
|
||||
val startMs = System.currentTimeMillis()
|
||||
val msPerRotation = 800L // one full rotation every 0.8 seconds
|
||||
|
||||
fun angleAt(now: Long) = ((now - startMs) % msPerRotation) * 360f / msPerRotation
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
ClaudeUsageWidget.currentRotation = angleAt(System.currentTimeMillis())
|
||||
ids.forEach { id -> ClaudeUsageWidget.updateWidget(context, manager, id) }
|
||||
delay(16) // aim for ~60fps; IPC speed sets the real ceiling
|
||||
}
|
||||
} finally {
|
||||
// Finish the current rotation cleanly — run until at least one full spin
|
||||
withContext(NonCancellable) {
|
||||
val minEndMs = startMs + msPerRotation
|
||||
while (System.currentTimeMillis() < minEndMs) {
|
||||
ClaudeUsageWidget.currentRotation = angleAt(System.currentTimeMillis())
|
||||
ids.forEach { id -> ClaudeUsageWidget.updateWidget(context, manager, id) }
|
||||
delay(16)
|
||||
}
|
||||
}
|
||||
angle = (angle + 30f) % 360f
|
||||
delay(83) // ~12 fps, one full rotation per second
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushWidgetUpdate() {
|
||||
ClaudeUsageWidget.isRefreshing = false
|
||||
ClaudeUsageWidget.currentRotation = 0f
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
val ids = manager.getAppWidgetIds(ComponentName(context, ClaudeUsageWidget::class.java))
|
||||
ids.forEach { id -> ClaudeUsageWidget.updateWidget(context, manager, id) }
|
||||
@@ -88,7 +90,8 @@ 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(
|
||||
|
||||
Reference in New Issue
Block a user