Initial release: Claude Pro usage widget for Android
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package me.khodak.claudeusage.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
import com.google.gson.Gson
|
||||
import java.util.Calendar
|
||||
|
||||
class PreferencesManager(context: Context) {
|
||||
|
||||
private val gson = Gson()
|
||||
|
||||
private val securePrefs = try {
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
EncryptedSharedPreferences.create(
|
||||
context, "claude_secure", masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
context.getSharedPreferences("claude_secure_fallback", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private val prefs = context.getSharedPreferences("claude_prefs", Context.MODE_PRIVATE)
|
||||
|
||||
fun saveCookies(cookies: String) {
|
||||
securePrefs.edit().putString(KEY_COOKIES, cookies).apply()
|
||||
// Plaintext backup — survives EncryptedSharedPreferences key rotation on reinstall
|
||||
prefs.edit().putString(KEY_COOKIES_BACKUP, cookies).apply()
|
||||
}
|
||||
|
||||
fun getCookies(): String? =
|
||||
securePrefs.getString(KEY_COOKIES, null)
|
||||
?: prefs.getString(KEY_COOKIES_BACKUP, null)
|
||||
|
||||
fun clearSession() {
|
||||
securePrefs.edit().clear().apply()
|
||||
prefs.edit().remove(KEY_ORG_ID).remove(KEY_SESSION_START)
|
||||
.remove(KEY_COOKIES_BACKUP).apply()
|
||||
}
|
||||
|
||||
fun saveOrgId(id: String) = prefs.edit().putString(KEY_ORG_ID, id).apply()
|
||||
fun getOrgId(): String? = prefs.getString(KEY_ORG_ID, null)
|
||||
|
||||
fun saveSessionStart(epochMs: Long) = prefs.edit().putLong(KEY_SESSION_START, epochMs).apply()
|
||||
fun getSessionStart(): Long = prefs.getLong(KEY_SESSION_START, 0L)
|
||||
|
||||
fun markTodayActive() {
|
||||
val cal = Calendar.getInstance()
|
||||
val dayBit = 1 shl cal.get(Calendar.DAY_OF_WEEK) - 1
|
||||
val weekKey = getWeekKey(cal)
|
||||
// Reset mask if it's a new week
|
||||
val storedWeek = prefs.getString(KEY_ACTIVE_WEEK, "")
|
||||
val currentMask = if (storedWeek == weekKey) prefs.getInt(KEY_ACTIVE_MASK, 0) else 0
|
||||
prefs.edit()
|
||||
.putString(KEY_ACTIVE_WEEK, weekKey)
|
||||
.putInt(KEY_ACTIVE_MASK, currentMask or dayBit)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun getWeeklyMask(): Int {
|
||||
val cal = Calendar.getInstance()
|
||||
val weekKey = getWeekKey(cal)
|
||||
val storedWeek = prefs.getString(KEY_ACTIVE_WEEK, "")
|
||||
return if (storedWeek == weekKey) prefs.getInt(KEY_ACTIVE_MASK, 0) else 0
|
||||
}
|
||||
|
||||
private fun getWeekKey(cal: Calendar): String {
|
||||
val year = cal.get(Calendar.YEAR)
|
||||
val week = cal.get(Calendar.WEEK_OF_YEAR)
|
||||
return "$year-$week"
|
||||
}
|
||||
|
||||
fun saveUsageData(data: UsageData) =
|
||||
prefs.edit().putString(KEY_USAGE_DATA, gson.toJson(data)).apply()
|
||||
|
||||
fun getUsageData(): UsageData? = try {
|
||||
prefs.getString(KEY_USAGE_DATA, null)?.let { gson.fromJson(it, UsageData::class.java) }
|
||||
} catch (e: Exception) { null }
|
||||
|
||||
fun isLoggedIn(): Boolean = !getCookies().isNullOrBlank()
|
||||
|
||||
companion object {
|
||||
private const val KEY_COOKIES = "session_cookies"
|
||||
private const val KEY_COOKIES_BACKUP = "session_cookies_backup"
|
||||
private const val KEY_ORG_ID = "org_id"
|
||||
private const val KEY_SESSION_START = "session_start"
|
||||
private const val KEY_USAGE_DATA = "usage_data"
|
||||
private const val KEY_ACTIVE_WEEK = "active_week"
|
||||
private const val KEY_ACTIVE_MASK = "active_mask"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user