Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b69dd2b2 | |||
| 8965477cc7 | |||
| f6f7accfa5 | |||
| d86ffabb98 | |||
| fafa5a3bb7 |
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "me.khodak.claudeusage"
|
applicationId = "me.khodak.claudeusage"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 2
|
versionCode = 6
|
||||||
versionName = "1.1"
|
versionName = "1.5"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
|||||||
companion object {
|
companion object {
|
||||||
const val ACTION_REFRESH = "me.khodak.claudeusage.ACTION_REFRESH"
|
const val ACTION_REFRESH = "me.khodak.claudeusage.ACTION_REFRESH"
|
||||||
@Volatile internal var isRefreshing = false
|
@Volatile internal var isRefreshing = false
|
||||||
|
@Volatile internal var currentRotation = 0f
|
||||||
|
|
||||||
fun updateWidget(context: Context, manager: AppWidgetManager, widgetId: Int) {
|
fun updateWidget(context: Context, manager: AppWidgetManager, widgetId: Int) {
|
||||||
val prefs = PreferencesManager(context)
|
val prefs = PreferencesManager(context)
|
||||||
@@ -149,6 +150,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
|||||||
if (status.isNotBlank()) status else if (updatedMs > 0) formatTime(updatedMs) else "")
|
if (status.isNotBlank()) status else if (updatedMs > 0) formatTime(updatedMs) else "")
|
||||||
v.setInt(R.id.btn_refresh, "setColorFilter",
|
v.setInt(R.id.btn_refresh, "setColorFilter",
|
||||||
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
|
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
|
||||||
|
v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +232,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
|||||||
)
|
)
|
||||||
v.setInt(R.id.btn_refresh, "setColorFilter",
|
v.setInt(R.id.btn_refresh, "setColorFilter",
|
||||||
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
|
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
|
||||||
|
v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,12 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import androidx.work.*
|
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 me.khodak.claudeusage.data.PreferencesManager
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class UsageUpdateWorker(
|
class UsageUpdateWorker(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -22,21 +26,50 @@ class UsageUpdateWorker(
|
|||||||
|
|
||||||
prefs.markTodayActive()
|
prefs.markTodayActive()
|
||||||
|
|
||||||
// Try API — save result (even on failure, local data is preserved)
|
coroutineScope {
|
||||||
|
val animJob = launch { rotateRefreshIcon() }
|
||||||
try {
|
try {
|
||||||
val data = UsageRepository(prefs).fetchUsage()
|
val data = UsageRepository(prefs).fetchUsage()
|
||||||
prefs.saveUsageData(data)
|
prefs.saveUsageData(data)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {}
|
||||||
// API failed — don't save, keep existing cached data
|
animJob.cancel()
|
||||||
|
animJob.join()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always push widget update using latest prefs + cached api data
|
|
||||||
pushWidgetUpdate()
|
pushWidgetUpdate()
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun rotateRefreshIcon() {
|
||||||
|
val manager = AppWidgetManager.getInstance(context)
|
||||||
|
val ids = manager.getAppWidgetIds(ComponentName(context, ClaudeUsageWidget::class.java))
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun pushWidgetUpdate() {
|
private fun pushWidgetUpdate() {
|
||||||
ClaudeUsageWidget.isRefreshing = false
|
ClaudeUsageWidget.isRefreshing = false
|
||||||
|
ClaudeUsageWidget.currentRotation = 0f
|
||||||
val manager = AppWidgetManager.getInstance(context)
|
val manager = AppWidgetManager.getInstance(context)
|
||||||
val ids = manager.getAppWidgetIds(ComponentName(context, ClaudeUsageWidget::class.java))
|
val ids = manager.getAppWidgetIds(ComponentName(context, ClaudeUsageWidget::class.java))
|
||||||
ids.forEach { id -> ClaudeUsageWidget.updateWidget(context, manager, id) }
|
ids.forEach { id -> ClaudeUsageWidget.updateWidget(context, manager, id) }
|
||||||
|
|||||||
Reference in New Issue
Block a user