feat(connect): saved connection profiles support (#107) (#191)

* feat(#107): implement saved connection profiles support

* fix(connect): fallback profiles resolution and stubbing in tests
This commit is contained in:
M57 authored and GitHub committed 2026-06-21 00:18:35 +03:00
1 parent 0eed12d18e
commit 00c6e75f50
8 files changed
+479 -13

No files matched your search

@@ -26,6 +26,8 @@ object AuthManager {
private const val KEY_BOTTOM_NAV_ITEMS = "bottom_nav_items"
private const val KEY_TYPING_EFFECT_ENABLED = "typing_effect_enabled"
private const val KEY_TYPING_EFFECT_DELAY_MS = "typing_effect_delay_ms"
private const val KEY_CONNECTION_PROFILES = "connection_profiles"
private const val KEY_SELECTED_PROFILE_ID = "selected_profile_id"
private const val DEFAULT_HOST = "127.0.0.1"
private const val DEFAULT_PORT = 9119
@@ -39,6 +41,8 @@ object AuthManager {
private val _bottomNavItemsFlow = MutableStateFlow<List<String>>(emptyList())
val bottomNavItemsFlow: StateFlow<List<String>> = _bottomNavItemsFlow.asStateFlow()
private val gson = com.google.gson.Gson()
/**
* Initialise the encrypted preferences.
* Call this once from Application.onCreate() or MainActivity.onCreate().
@@ -71,28 +75,112 @@ object AuthManager {
"AuthManager not initialised – call AuthManager.init(context) first",
)
// ── Connection Profiles ──────────────────────────────────────────────
fun getConnectionProfiles(): List<com.m57.hermescontrol.data.model.ConnectionProfile> {
val json = requirePrefs().getString(KEY_CONNECTION_PROFILES, null) ?: return emptyList()
return try {
val type =
object : com.google.gson.reflect.TypeToken<
List<com.m57.hermescontrol.data.model.ConnectionProfile>,
>() {}.type
gson.fromJson(json, type) ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
fun saveConnectionProfiles(profiles: List<com.m57.hermescontrol.data.model.ConnectionProfile>) {
val json = gson.toJson(profiles)
requirePrefs().edit().putString(KEY_CONNECTION_PROFILES, json).apply()
}
fun getProfileToken(profileId: String): String? {
return requirePrefs().getString("token_$profileId", null)
}
fun setProfileToken(
profileId: String,
token: String?,
) {
requirePrefs().edit().putString("token_$profileId", token).apply()
}
fun getSelectedProfileId(): String? {
val id = requirePrefs().getString(KEY_SELECTED_PROFILE_ID, null)
return if (id.isNullOrBlank()) null else id
}
fun setSelectedProfileId(id: String?) {
requirePrefs().edit().putString(KEY_SELECTED_PROFILE_ID, id).apply()
}
// ── Token ────────────────────────────────────────────────────────────
fun getToken(): String? = requirePrefs().getString(KEY_TOKEN, null)
fun getToken(): String? {
val selectedId = getSelectedProfileId()
if (selectedId != null) {
val token = getProfileToken(selectedId)
if (token != null) return token
}
return requirePrefs().getString(KEY_TOKEN, null)
}
fun setToken(token: String?) {
requirePrefs().edit().putString(KEY_TOKEN, token).apply()
val selectedId = getSelectedProfileId()
if (selectedId != null) {
setProfileToken(selectedId, token)
} else {
requirePrefs().edit().putString(KEY_TOKEN, token).apply()
}
}
// ── Host ─────────────────────────────────────────────────────────────
fun getHost(): String = requirePrefs().getString(KEY_HOST, DEFAULT_HOST) ?: DEFAULT_HOST
fun getHost(): String {
val selectedId = getSelectedProfileId()
if (selectedId != null) {
val profile = getConnectionProfiles().firstOrNull { it.id == selectedId }
if (profile != null) return profile.host
}
return requirePrefs().getString(KEY_HOST, DEFAULT_HOST) ?: DEFAULT_HOST
}
fun setHost(host: String) {
requirePrefs().edit().putString(KEY_HOST, host).apply()
val selectedId = getSelectedProfileId()
if (selectedId != null) {
val profiles =
getConnectionProfiles().map {
if (it.id == selectedId) it.copy(host = host) else it
}
saveConnectionProfiles(profiles)
} else {
requirePrefs().edit().putString(KEY_HOST, host).apply()
}
}
// ── Port ─────────────────────────────────────────────────────────────
fun getPort(): Int = requirePrefs().getInt(KEY_PORT, DEFAULT_PORT)
fun getPort(): Int {
val selectedId = getSelectedProfileId()
if (selectedId != null) {
val profile = getConnectionProfiles().firstOrNull { it.id == selectedId }
if (profile != null) return profile.port
}
return requirePrefs().getInt(KEY_PORT, DEFAULT_PORT)
}
fun setPort(port: Int) {
requirePrefs().edit().putInt(KEY_PORT, port).apply()
val selectedId = getSelectedProfileId()
if (selectedId != null) {
val profiles =
getConnectionProfiles().map {
if (it.id == selectedId) it.copy(port = port) else it
}
saveConnectionProfiles(profiles)
} else {
requirePrefs().edit().putInt(KEY_PORT, port).apply()
}
}
// ── Auto-reconnect ───────────────────────────────────────────────────
@@ -0,0 +1,10 @@
package com.m57.hermescontrol.data.model
import java.util.UUID
data class ConnectionProfile(
val id: String = UUID.randomUUID().toString(),
val name: String,
val host: String,
val port: Int,
)