fuzzyworld

ai-chat-android-auto / files / app / src / main / java / net / fuzzyworld / aichat / Prefs.kt

Prefs.kt

5.2 KB · history · blame

1package net.fuzzyworld.aichat
2
3import android.content.Context
4import android.content.SharedPreferences
5import androidx.security.crypto.EncryptedSharedPreferences
6import androidx.security.crypto.MasterKey
7import org.json.JSONArray
8import org.json.JSONObject
9
10object Prefs {
11 private const val FILE = "ai_auto_prefs"
12 private const val KEY_PROVIDER = "provider_id"
13
14 private fun prefs(context: Context): SharedPreferences {
15 val master = MasterKey.Builder(context)
16 .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
17 .build()
18 return EncryptedSharedPreferences.create(
19 context, FILE, master,
20 EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
21 EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
22 )
23 }
24
25 // ── Provider selection ─────────────────────────────────────────────────────
26
27 fun selectedProviderId(context: Context): String =
28 prefs(context).getString(KEY_PROVIDER, AnthropicProvider.id) ?: AnthropicProvider.id
29
30 fun saveProviderId(context: Context, id: String) =
31 prefs(context).edit().putString(KEY_PROVIDER, id).apply()
32
33 // ── Per-provider API key ───────────────────────────────────────────────────
34
35 fun apiKey(context: Context, providerId: String = selectedProviderId(context)): String =
36 prefs(context).getString("key_$providerId", "") ?: ""
37
38 fun hasApiKey(context: Context, providerId: String = selectedProviderId(context)): Boolean =
39 apiKey(context, providerId).isNotBlank()
40
41 fun saveApiKey(context: Context, providerId: String, key: String) =
42 prefs(context).edit().putString("key_$providerId", key).apply()
43
44 fun clearApiKey(context: Context, providerId: String) =
45 prefs(context).edit().remove("key_$providerId").apply()
46
47 // ── Per-provider base URL (with fallback to provider default) ──────────────
48
49 fun baseUrl(context: Context, providerId: String = selectedProviderId(context)): String {
50 val default = ProviderRegistry.byId(providerId).defaultBaseUrl
51 return prefs(context).getString("url_$providerId", default)?.ifBlank { default } ?: default
52 }
53
54 fun saveBaseUrl(context: Context, providerId: String, url: String) =
55 prefs(context).edit().putString("url_$providerId", url).apply()
56
57 // ── Per-provider selected model (with fallback to provider default) ─────────
58
59 fun model(context: Context, providerId: String = selectedProviderId(context)): String {
60 val default = ProviderRegistry.byId(providerId).defaultModel
61 return prefs(context).getString("model_$providerId", default)?.ifBlank { default } ?: default
62 }
63
64 fun saveModel(context: Context, providerId: String, modelId: String) =
65 prefs(context).edit().putString("model_$providerId", modelId).apply()
66
67 // ── Cached model list ──────────────────────────────────────────────────────
68
69 fun cachedModels(context: Context, providerId: String): List<ModelInfo>? {
70 val json = prefs(context).getString("models_$providerId", null) ?: return null
71 return try {
72 val arr = JSONArray(json)
73 (0 until arr.length()).map { i ->
74 val obj = arr.getJSONObject(i)
75 ModelInfo(obj.getString("id"), obj.getString("displayName"))
76 }
77 } catch (_: Exception) { null }
78 }
79
80 fun saveModelCache(context: Context, providerId: String, models: List<ModelInfo>) {
81 val arr = JSONArray().apply {
82 models.forEach { m ->
83 put(JSONObject().apply {
84 put("id", m.id)
85 put("displayName", m.displayName)
86 })
87 }
88 }
89 prefs(context).edit().putString("models_$providerId", arr.toString()).apply()
90 }
91
92 // ── Voice output settings ──────────────────────────────────────────────────
93
94 fun voiceId(context: Context): String =
95 prefs(context).getString("voice_id", "") ?: ""
96
97 fun saveVoiceId(context: Context, id: String) =
98 prefs(context).edit().putString("voice_id", id).apply()
99
100 fun speechRate(context: Context): Float =
101 prefs(context).getFloat("speech_rate", 1.0f)
102
103 fun saveSpeechRate(context: Context, rate: Float) =
104 prefs(context).edit().putFloat("speech_rate", rate).apply()
105
106 // ── Ready check (used by the media service) ────────────────────────────────
107
108 fun isReady(context: Context): Boolean {
109 val providerId = selectedProviderId(context)
110 val provider = ProviderRegistry.byId(providerId)
111 if (provider.requiresApiKey && !hasApiKey(context, providerId)) return false
112 if (provider.allowsBaseUrlOverride && baseUrl(context, providerId).isBlank()) return false
113 return model(context, providerId).isNotBlank()
114 }
115}