@@ -8,6 +8,8 @@ import androidx.datastore.preferences.core.*
88import androidx.datastore.preferences.preferencesDataStore
99import androidx.security.crypto.EncryptedSharedPreferences
1010import androidx.security.crypto.MasterKey
11+import com.parseable.android.data.local.SavedServer
12+import com.parseable.android.data.local.SavedServerDao
1113import com.parseable.android.data.model.ServerConfig
1214import dagger.hilt.android.qualifiers.ApplicationContext
1315import kotlinx.coroutines.Dispatchers
@@ -27,10 +29,12 @@ private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(na
2729@Singleton
2830class SettingsRepository @Inject constructor(
2931 @ApplicationContext private val context: Context,
32+ private val savedServerDao: SavedServerDao,
3033) {
3134 private val serverUrlKey = stringPreferencesKey("server_url")
3235 private val usernameKey = stringPreferencesKey("username")
3336 private val useTlsKey = booleanPreferencesKey("use_tls")
37+ private val activeServerIdKey = longPreferencesKey("active_server_id")
3438
3539 /** Guards concurrent reads/writes across DataStore and EncryptedSharedPreferences. */
3640 private val configMutex = Mutex()
@@ -62,6 +66,8 @@ class SettingsRepository @Inject constructor(
6266 )
6367 }
6468
69+ // ── Active server config (current connection) ──────────────────────
70+
6571 val serverConfig: Flow<ServerConfig?> = context.dataStore.data
6672 .catch { e ->
6773 Timber.e(e, "Failed to read DataStore")
@@ -87,6 +93,14 @@ class SettingsRepository @Inject constructor(
8793 }
8894 .flowOn(Dispatchers.IO)
8995
96+ val activeServerId: Flow<Long?> = context.dataStore.data
97+ .catch { e ->
98+ Timber.e(e, "Failed to read DataStore")
99+ emit(emptyPreferences())
100+ }
101+ .map { prefs -> prefs[activeServerIdKey] }
102+ .flowOn(Dispatchers.IO)
103+
90104 suspend fun getSavedPassword(): ServerConfig? = serverConfig.first()
91105
92106 suspend fun saveServerConfig(config: ServerConfig) {
@@ -109,13 +123,111 @@ class SettingsRepository @Inject constructor(
109123 suspend fun clearConfig() {
110124 configMutex.withLock {
111125 try {
112− context.dataStore.edit { it.clear() }
126+ context.dataStore.edit { prefs ->
127+ prefs.remove(serverUrlKey)
128+ prefs.remove(usernameKey)
129+ prefs.remove(useTlsKey)
130+ prefs.remove(activeServerIdKey)
131+ }
113132 withContext(Dispatchers.IO) {
114− encryptedPrefs.edit().clear().apply()
133+ encryptedPrefs.edit().remove("password").apply()
115134 }
116135 } catch (e: Exception) {
117136 Timber.e(e, "Failed to clear config")
118137 }
119138 }
120139 }
140+
141+ // ── Multi-server management ────────────────────────────────────────
142+
143+ val savedServers: Flow<List<SavedServer>> = savedServerDao.getAll()
144+
145+ /**
146+ * Persist the current server connection as a saved server entry.
147+ * If a server with the same URL and username already exists, updates it.
148+ * Returns the server ID.
149+ */
150+ suspend fun saveServer(config: ServerConfig): Long {
151+ return configMutex.withLock {
152+ val existing = savedServerDao.findByUrlAndUsername(config.serverUrl, config.username)
153+ val passwordKey = existing?.passwordKey ?: "server_pwd_${System.currentTimeMillis()}"
154+ val server = SavedServer(
155+ id = existing?.id ?: 0,
156+ serverUrl = config.serverUrl,
157+ username = config.username,
158+ useTls = config.useTls,
159+ passwordKey = passwordKey,
160+ addedAt = existing?.addedAt ?: System.currentTimeMillis(),
161+ )
162+ withContext(Dispatchers.IO) {
163+ encryptedPrefs.edit().putString(passwordKey, config.password).apply()
164+ }
165+ val id = savedServerDao.insert(server)
166+
167+ // Update active server ID
168+ context.dataStore.edit { prefs ->
169+ prefs[activeServerIdKey] = id
170+ }
171+ id
172+ }
173+ }
174+
175+ /**
176+ * Switch to a previously saved server. Loads its credentials into the
177+ * active config (DataStore + encrypted password).
178+ */
179+ suspend fun switchToServer(serverId: Long): ServerConfig? {
180+ return configMutex.withLock {
181+ val server = savedServerDao.getById(serverId) ?: return@withLock null
182+ val password = withContext(Dispatchers.IO) {
183+ try {
184+ encryptedPrefs.getString(server.passwordKey, null)
185+ } catch (e: Exception) {
186+ Timber.e(e, "Failed to read server password")
187+ null
188+ }
189+ } ?: return@withLock null
190+
191+ val config = ServerConfig(
192+ serverUrl = server.serverUrl,
193+ username = server.username,
194+ password = password,
195+ useTls = server.useTls,
196+ )
197+
198+ // Write to active config
199+ context.dataStore.edit { prefs ->
200+ prefs[serverUrlKey] = config.serverUrl
201+ prefs[usernameKey] = config.username
202+ prefs[useTlsKey] = config.useTls
203+ prefs[activeServerIdKey] = serverId
204+ }
205+ withContext(Dispatchers.IO) {
206+ encryptedPrefs.edit().putString("password", config.password).apply()
207+ }
208+ config
209+ }
210+ }
211+
212+ /**
213+ * Remove a saved server and its stored password.
214+ * If the deleted server was the active one, the active server ID is cleared.
215+ */
216+ suspend fun deleteServer(serverId: Long) {
217+ configMutex.withLock {
218+ val server = savedServerDao.getById(serverId) ?: return@withLock
219+ withContext(Dispatchers.IO) {
220+ encryptedPrefs.edit().remove(server.passwordKey).apply()
221+ }
222+ savedServerDao.deleteById(serverId)
223+
224+ // Clear active server reference if it pointed at the deleted server
225+ val currentActiveId = context.dataStore.data.first()[activeServerIdKey]
226+ if (currentActiveId == serverId) {
227+ context.dataStore.edit { prefs ->
228+ prefs.remove(activeServerIdKey)
229+ }
230+ }
231+ }
232+ }
121233}