Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c69147530e | |||
| 5a5f6ed1e4 | |||
| a43fa5be92 | |||
| 1d4356c1d7 |
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "me.khodak.claudeusage"
|
applicationId = "me.khodak.claudeusage"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 15
|
versionCode = 19
|
||||||
versionName = "1.14"
|
versionName = "1.18"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -103,43 +103,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun setupNotificationSettings() {
|
private fun setupNotificationSettings() {
|
||||||
binding.switchNotify.isChecked = prefs.isNotifyEnabled()
|
binding.switchNotify.isChecked = prefs.isNotifyEnabled()
|
||||||
binding.sliderSession.value = prefs.getSessionThreshold().toFloat().coerceIn(50f, 100f)
|
|
||||||
binding.sliderWeekly.value = prefs.getWeeklyThreshold().toFloat().coerceIn(50f, 100f)
|
|
||||||
applyThresholdLabels()
|
|
||||||
applyNotifyControlsEnabled(prefs.isNotifyEnabled())
|
|
||||||
|
|
||||||
binding.switchNotify.setOnCheckedChangeListener { _, checked ->
|
binding.switchNotify.setOnCheckedChangeListener { _, checked ->
|
||||||
prefs.setNotifyEnabled(checked)
|
prefs.setNotifyEnabled(checked)
|
||||||
applyNotifyControlsEnabled(checked)
|
|
||||||
if (checked) requestNotificationPermission()
|
if (checked) requestNotificationPermission()
|
||||||
}
|
}
|
||||||
binding.sliderSession.addOnChangeListener { _, value, _ ->
|
|
||||||
prefs.setSessionThreshold(value.toInt())
|
|
||||||
applyThresholdLabels()
|
|
||||||
}
|
|
||||||
binding.sliderWeekly.addOnChangeListener { _, value, _ ->
|
|
||||||
prefs.setWeeklyThreshold(value.toInt())
|
|
||||||
applyThresholdLabels()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alerts default on, so prompt for the runtime permission once on first launch
|
// Alerts default on, so prompt for the runtime permission once on first launch
|
||||||
// (a user who never toggles the switch would otherwise never be asked).
|
// (a user who never toggles the switch would otherwise never be asked).
|
||||||
if (prefs.isNotifyEnabled()) requestNotificationPermission()
|
if (prefs.isNotifyEnabled()) requestNotificationPermission()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyThresholdLabels() {
|
|
||||||
binding.tvSessionThreshLabel.text = "Session alert at ${prefs.getSessionThreshold()}%"
|
|
||||||
binding.tvWeeklyThreshLabel.text = "Weekly alert at ${prefs.getWeeklyThreshold()}%"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyNotifyControlsEnabled(enabled: Boolean) {
|
|
||||||
binding.sliderSession.isEnabled = enabled
|
|
||||||
binding.sliderWeekly.isEnabled = enabled
|
|
||||||
val alpha = if (enabled) 1f else 0.4f
|
|
||||||
binding.tvSessionThreshLabel.alpha = alpha
|
|
||||||
binding.tvWeeklyThreshLabel.alpha = alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestNotificationPermission() {
|
private fun requestNotificationPermission() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
@@ -169,7 +141,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val merged = fresh.mergedWith(prefs.getUsageData())
|
val merged = fresh.mergedWith(prefs.getUsageData())
|
||||||
prefs.saveUsageData(merged)
|
prefs.saveUsageData(merged)
|
||||||
prefs.recordHistory(fresh)
|
prefs.recordHistory(fresh)
|
||||||
Notifier.checkAndNotify(this, prefs, fresh)
|
// Note: alerts fire only from the background worker, not here — no point pinging you
|
||||||
|
// with a notification while you're already looking at the app.
|
||||||
updateUI(merged)
|
updateUI(merged)
|
||||||
ClaudeUsageWidget.notifyDataChanged(this) // opening the app refreshes the widget too
|
ClaudeUsageWidget.notifyDataChanged(this) // opening the app refreshes the widget too
|
||||||
if (binding.tvDebugInfo.visibility == View.VISIBLE) {
|
if (binding.tvDebugInfo.visibility == View.VISIBLE) {
|
||||||
|
|||||||
@@ -12,16 +12,18 @@ import me.khodak.claudeusage.data.PreferencesManager
|
|||||||
import me.khodak.claudeusage.data.UsageData
|
import me.khodak.claudeusage.data.UsageData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts a notification when session or weekly utilization crosses the user's threshold.
|
* Fires exactly two alerts per metric — one at 90% and one at 100% — and no more.
|
||||||
* Each metric fires at most once per limit window: we remember the reset-epoch we alerted
|
*
|
||||||
* for, and only re-arm when that window rolls over (epoch changes) — so the user isn't
|
* Uses hysteresis, not the API's reset timestamp: each level fires once when usage first
|
||||||
* pinged every 5 minutes while sitting above the line.
|
* crosses it, and only re-arms after usage drops back below that level (i.e. a new window /
|
||||||
|
* usage reset). That keeps it quiet even if the reset time drifts between fetches. Only the
|
||||||
|
* background worker calls this — never the in-app refresh loop — so you're not pinged while
|
||||||
|
* you're already looking at the app.
|
||||||
*/
|
*/
|
||||||
object Notifier {
|
object Notifier {
|
||||||
|
|
||||||
private const val CHANNEL_ID = "usage_alerts"
|
private const val CHANNEL_ID = "usage_alerts"
|
||||||
private const val SESSION_NOTIF_ID = 2001
|
private val LEVELS = intArrayOf(90, 100)
|
||||||
private const val WEEKLY_NOTIF_ID = 2002
|
|
||||||
|
|
||||||
fun checkAndNotify(context: Context, prefs: PreferencesManager, data: UsageData) {
|
fun checkAndNotify(context: Context, prefs: PreferencesManager, data: UsageData) {
|
||||||
if (!prefs.isNotifyEnabled()) return
|
if (!prefs.isNotifyEnabled()) return
|
||||||
@@ -29,53 +31,50 @@ object Notifier {
|
|||||||
if (!mgr.areNotificationsEnabled()) return // OS-level or runtime permission off
|
if (!mgr.areNotificationsEnabled()) return // OS-level or runtime permission off
|
||||||
ensureChannel(context)
|
ensureChannel(context)
|
||||||
|
|
||||||
val session = data.fiveHourUtilization
|
evaluate(context, mgr, prefs, "session", data.fiveHourUtilization, "5-hour session")
|
||||||
if (session >= 0f) {
|
evaluate(context, mgr, prefs, "weekly", data.weeklyUtilization, "weekly")
|
||||||
maybeFire(
|
|
||||||
context, mgr, prefs,
|
|
||||||
key = "session",
|
|
||||||
util = session.toInt(),
|
|
||||||
threshold = prefs.getSessionThreshold(),
|
|
||||||
resetEpoch = data.effectiveResetEpoch,
|
|
||||||
notifId = SESSION_NOTIF_ID,
|
|
||||||
title = "Session usage at ${session.toInt()}%",
|
|
||||||
body = "Your current 5-hour window is nearly used up."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val weekly = data.weeklyUtilization
|
private fun evaluate(
|
||||||
if (weekly >= 0f) {
|
|
||||||
maybeFire(
|
|
||||||
context, mgr, prefs,
|
|
||||||
key = "weekly",
|
|
||||||
util = weekly.toInt(),
|
|
||||||
threshold = prefs.getWeeklyThreshold(),
|
|
||||||
resetEpoch = data.weeklyResetAtEpoch,
|
|
||||||
notifId = WEEKLY_NOTIF_ID,
|
|
||||||
title = "Weekly usage at ${weekly.toInt()}%",
|
|
||||||
body = "You're approaching your weekly Claude limit."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun maybeFire(
|
|
||||||
context: Context,
|
context: Context,
|
||||||
mgr: NotificationManagerCompat,
|
mgr: NotificationManagerCompat,
|
||||||
prefs: PreferencesManager,
|
prefs: PreferencesManager,
|
||||||
key: String,
|
metric: String,
|
||||||
util: Int,
|
utilization: Float,
|
||||||
threshold: Int,
|
label: String
|
||||||
resetEpoch: Long,
|
|
||||||
notifId: Int,
|
|
||||||
title: String,
|
|
||||||
body: String
|
|
||||||
) {
|
) {
|
||||||
if (util < threshold) return
|
if (utilization < 0f) return
|
||||||
// Already alerted for this exact window? Skip. (resetEpoch<=0 means "unknown window" —
|
val util = utilization.toInt()
|
||||||
// fall back to a coarse marker so we still alert once instead of never.)
|
for ((i, level) in LEVELS.withIndex()) {
|
||||||
val windowMarker = if (resetEpoch > 0) resetEpoch else 1L
|
val key = "${metric}_$level"
|
||||||
if (prefs.getNotifiedResetEpoch(key) == windowMarker) return
|
if (util >= level) {
|
||||||
|
if (!prefs.wasNotified(key)) {
|
||||||
|
fire(context, mgr, notifId(metric, i), level, label, util)
|
||||||
|
prefs.setNotified(key, true)
|
||||||
|
}
|
||||||
|
} else if (prefs.wasNotified(key)) {
|
||||||
|
prefs.setNotified(key, false) // dropped below the line → re-arm for next window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fire(
|
||||||
|
context: Context,
|
||||||
|
mgr: NotificationManagerCompat,
|
||||||
|
notifId: Int,
|
||||||
|
level: Int,
|
||||||
|
label: String,
|
||||||
|
util: Int
|
||||||
|
) {
|
||||||
|
val title: String
|
||||||
|
val body: String
|
||||||
|
if (level >= 100) {
|
||||||
|
title = "${label.replaceFirstChar { it.uppercase() }} limit reached"
|
||||||
|
body = "You've hit 100% of your $label limit."
|
||||||
|
} else {
|
||||||
|
title = "${label.replaceFirstChar { it.uppercase() }} at $util%"
|
||||||
|
body = "You're at $level% of your $label limit."
|
||||||
|
}
|
||||||
val notif = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notif = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_claude_burst)
|
.setSmallIcon(R.drawable.ic_claude_burst)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
@@ -84,15 +83,17 @@ object Notifier {
|
|||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setContentIntent(openAppIntent(context))
|
.setContentIntent(openAppIntent(context))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mgr.notify(notifId, notif)
|
mgr.notify(notifId, notif)
|
||||||
prefs.setNotifiedResetEpoch(key, windowMarker)
|
|
||||||
} catch (_: SecurityException) {
|
} catch (_: SecurityException) {
|
||||||
// Notifications revoked between the check and post — nothing to do.
|
// Notifications revoked between the check and post — nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stable id per (metric, level) so re-posts replace rather than stack.
|
||||||
|
private fun notifId(metric: String, levelIndex: Int) =
|
||||||
|
2000 + (if (metric == "weekly") 10 else 0) + levelIndex
|
||||||
|
|
||||||
private fun ensureChannel(context: Context) {
|
private fun ensureChannel(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
val mgr = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val mgr = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
@@ -100,7 +101,7 @@ object Notifier {
|
|||||||
mgr.createNotificationChannel(
|
mgr.createNotificationChannel(
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID, "Usage alerts", NotificationManager.IMPORTANCE_DEFAULT
|
CHANNEL_ID, "Usage alerts", NotificationManager.IMPORTANCE_DEFAULT
|
||||||
).apply { description = "Alerts when you approach your Claude usage limits" }
|
).apply { description = "Alerts at 90% and 100% of your Claude usage limits" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,19 +127,13 @@ class PreferencesManager(context: Context) {
|
|||||||
fun isNotifyEnabled(): Boolean = prefs.getBoolean(KEY_NOTIFY_ENABLED, true)
|
fun isNotifyEnabled(): Boolean = prefs.getBoolean(KEY_NOTIFY_ENABLED, true)
|
||||||
fun setNotifyEnabled(v: Boolean) = prefs.edit().putBoolean(KEY_NOTIFY_ENABLED, v).apply()
|
fun setNotifyEnabled(v: Boolean) = prefs.edit().putBoolean(KEY_NOTIFY_ENABLED, v).apply()
|
||||||
|
|
||||||
fun getSessionThreshold(): Int = prefs.getInt(KEY_NOTIFY_SESSION_PCT, 90)
|
|
||||||
fun setSessionThreshold(pct: Int) = prefs.edit().putInt(KEY_NOTIFY_SESSION_PCT, pct).apply()
|
|
||||||
|
|
||||||
fun getWeeklyThreshold(): Int = prefs.getInt(KEY_NOTIFY_WEEKLY_PCT, 85)
|
|
||||||
fun setWeeklyThreshold(pct: Int) = prefs.edit().putInt(KEY_NOTIFY_WEEKLY_PCT, pct).apply()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the reset-epoch a metric was last notified for, so we alert at most once
|
* Per-level "already alerted" flag (e.g. "session_90"). Set when the level is crossed,
|
||||||
* per limit window. When the window rolls over (reset epoch changes), it re-arms.
|
* cleared when usage drops back below it — so each level fires once per window.
|
||||||
*/
|
*/
|
||||||
fun getNotifiedResetEpoch(key: String): Long = prefs.getLong("notified_$key", 0L)
|
fun wasNotified(key: String): Boolean = prefs.getBoolean("notified_$key", false)
|
||||||
fun setNotifiedResetEpoch(key: String, epoch: Long) =
|
fun setNotified(key: String, v: Boolean) =
|
||||||
prefs.edit().putLong("notified_$key", epoch).apply()
|
prefs.edit().putBoolean("notified_$key", v).apply()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val KEY_COOKIES = "session_cookies"
|
private const val KEY_COOKIES = "session_cookies"
|
||||||
@@ -150,8 +144,6 @@ class PreferencesManager(context: Context) {
|
|||||||
private const val KEY_ACTIVE_MASK = "active_mask"
|
private const val KEY_ACTIVE_MASK = "active_mask"
|
||||||
private const val KEY_HISTORY = "usage_history"
|
private const val KEY_HISTORY = "usage_history"
|
||||||
private const val KEY_NOTIFY_ENABLED = "notify_enabled"
|
private const val KEY_NOTIFY_ENABLED = "notify_enabled"
|
||||||
private const val KEY_NOTIFY_SESSION_PCT = "notify_session_pct"
|
|
||||||
private const val KEY_NOTIFY_WEEKLY_PCT = "notify_weekly_pct"
|
|
||||||
private const val KEY_AUTH_FAILS = "auth_fail_count"
|
private const val KEY_AUTH_FAILS = "auth_fail_count"
|
||||||
|
|
||||||
private const val MIN_HISTORY_GAP_MS = 2 * 60 * 1000L // collapse readings <2 min apart
|
private const val MIN_HISTORY_GAP_MS = 2 * 60 * 1000L // collapse readings <2 min apart
|
||||||
|
|||||||
@@ -323,40 +323,13 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSessionThreshLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:text="Session alert at 90%"
|
|
||||||
android:textColor="#FFFFFF"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<com.google.android.material.slider.Slider
|
|
||||||
android:id="@+id/sliderSession"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:valueFrom="50"
|
android:layout_marginTop="12dp"
|
||||||
android:valueTo="100"
|
android:text="One alert at 90% and one at 100%, for both your session and weekly limits. Each fires once until usage resets."
|
||||||
android:stepSize="5"
|
android:textColor="#AAAAAA"
|
||||||
android:value="90" />
|
android:textSize="13sp"
|
||||||
|
android:lineSpacingExtra="3dp" />
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvWeeklyThreshLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:text="Weekly alert at 85%"
|
|
||||||
android:textColor="#FFFFFF"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<com.google.android.material.slider.Slider
|
|
||||||
android:id="@+id/sliderWeekly"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:valueFrom="50"
|
|
||||||
android:valueTo="100"
|
|
||||||
android:stepSize="5"
|
|
||||||
android:value="85" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_bg" />
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_bg" />
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 17 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 28 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/claude_orange" />
|
|
||||||
<foreground>
|
|
||||||
<inset
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:drawable="@drawable/ic_launcher_fg"
|
|
||||||
android:inset="25%" />
|
|
||||||
</foreground>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -5,4 +5,5 @@
|
|||||||
<color name="surface_dark">#1E1E1E</color>
|
<color name="surface_dark">#1E1E1E</color>
|
||||||
<color name="text_primary">#FFFFFF</color>
|
<color name="text_primary">#FFFFFF</color>
|
||||||
<color name="text_secondary">#888888</color>
|
<color name="text_secondary">#888888</color>
|
||||||
|
<color name="ic_launcher_bg">#0B1D27</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||