diff --git a/Android/app/build.gradle.kts b/Android/app/build.gradle.kts index 5e3c414b..777b96a9 100644 --- a/Android/app/build.gradle.kts +++ b/Android/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.archipelago.app" minSdk = 26 targetSdk = 35 - versionCode = 14 - versionName = "0.4.10" + versionCode = 15 + versionName = "0.4.11" vectorDrawables { useSupportLibrary = true diff --git a/Android/app/src/main/java/com/archipelago/app/data/ServerPreferences.kt b/Android/app/src/main/java/com/archipelago/app/data/ServerPreferences.kt index c5bc1049..255f0b05 100644 --- a/Android/app/src/main/java/com/archipelago/app/data/ServerPreferences.kt +++ b/Android/app/src/main/java/com/archipelago/app/data/ServerPreferences.kt @@ -112,6 +112,37 @@ class ServerPreferences(private val context: Context) { } } + /** + * Replace a saved server in place. Matches the existing entry by connection + * identity (address/port/scheme) so edits that change the name or password — + * or that touch a legacy 4-field entry — still update the right record. If the + * edited server is also the active one, the active record is kept in sync. + */ + suspend fun updateSavedServer(original: ServerEntry, updated: ServerEntry) { + context.dataStore.edit { prefs -> + val current = prefs[savedServersKey] ?: emptySet() + val filtered = current.filterNot { raw -> + val e = ServerEntry.deserialize(raw) + e != null && + e.address == original.address && + e.port == original.port && + e.useHttps == original.useHttps + }.toSet() + prefs[savedServersKey] = filtered + updated.serialize() + + val isActive = prefs[activeAddressKey] == original.address && + (prefs[activePortKey] ?: "") == original.port && + (prefs[activeHttpsKey] ?: false) == original.useHttps + if (isActive) { + prefs[activeAddressKey] = updated.address + prefs[activeHttpsKey] = updated.useHttps + prefs[activePortKey] = updated.port + prefs[activePasswordKey] = updated.password + prefs[activeNameKey] = updated.name + } + } + } + suspend fun removeSavedServer(server: ServerEntry) { context.dataStore.edit { prefs -> val current = prefs[savedServersKey] ?: emptySet() diff --git a/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt b/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt index 738eb4a8..2edaafe5 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.LockOpen import androidx.compose.material3.CircularProgressIndicator @@ -106,9 +107,50 @@ fun ServerConnectScreen( var useHttps by remember { mutableStateOf(false) } var isConnecting by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } + // The saved server currently being edited, or null when adding/connecting. + var editingServer by remember { mutableStateOf(null) } val savedServers by prefs.savedServers.collectAsState(initial = emptyList()) + fun clearForm() { + name = "" + address = "" + port = "" + password = "" + useHttps = false + passwordVisible = false + errorMessage = null + } + + fun startEdit(server: ServerEntry) { + editingServer = server + name = server.name + address = server.address + port = server.port + password = server.password + useHttps = server.useHttps + passwordVisible = false + errorMessage = null + } + + fun cancelEdit() { + editingServer = null + clearForm() + } + + fun saveEdit() { + val original = editingServer ?: return + if (address.isBlank()) { + errorMessage = "Enter a server address" + return + } + val updated = ServerEntry(address, useHttps, port, password, name) + scope.launch { + prefs.updateSavedServer(original, updated) + cancelEdit() + } + } + fun connect(server: ServerEntry) { if (isConnecting) return if (server.address.isBlank()) { @@ -178,7 +220,7 @@ fun ServerConnectScreen( Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Connect to Server", + text = if (editingServer != null) stringResource(R.string.edit_server_title) else "Connect to Server", style = MaterialTheme.typography.headlineMedium, color = TextPrimary, textAlign = TextAlign.Center, @@ -324,7 +366,11 @@ fun ServerConnectScreen( keyboardActions = KeyboardActions( onGo = { keyboard?.hide() - connect(ServerEntry(address, useHttps, port, password, name)) + if (editingServer != null) { + saveEdit() + } else { + connect(ServerEntry(address, useHttps, port, password, name)) + } }, ), colors = OutlinedTextFieldDefaults.colors( @@ -389,15 +435,40 @@ fun ServerConnectScreen( } } - // Connect button — glass style - GlassButton( - text = if (isConnecting) stringResource(R.string.connecting) else stringResource(R.string.connect), - onClick = { - keyboard?.hide() - connect(ServerEntry(address, useHttps, port, password, name)) - }, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) + if (editingServer != null) { + // Save / Cancel while editing an existing saved server + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + GlassButton( + text = stringResource(R.string.cancel), + onClick = { + keyboard?.hide() + cancelEdit() + }, + modifier = Modifier.weight(1f).height(56.dp), + ) + GlassButton( + text = stringResource(R.string.save_changes), + onClick = { + keyboard?.hide() + saveEdit() + }, + modifier = Modifier.weight(1f).height(56.dp), + ) + } + } else { + // Connect button — glass style + GlassButton( + text = if (isConnecting) stringResource(R.string.connecting) else stringResource(R.string.connect), + onClick = { + keyboard?.hide() + connect(ServerEntry(address, useHttps, port, password, name)) + }, + modifier = Modifier.fillMaxWidth().height(56.dp), + ) + } if (isConnecting) { CircularProgressIndicator( @@ -407,8 +478,8 @@ fun ServerConnectScreen( ) } - // Saved servers - if (savedServers.isNotEmpty()) { + // Saved servers (hidden while editing one to keep focus on the form) + if (editingServer == null && savedServers.isNotEmpty()) { Spacer(modifier = Modifier.height(8.dp)) Text( text = stringResource(R.string.saved_servers), @@ -422,6 +493,7 @@ fun ServerConnectScreen( SavedServerItem( server = server, onConnect = { connect(it) }, + onEdit = { startEdit(it) }, onRemove = { scope.launch { prefs.removeSavedServer(it) } }, ) } @@ -434,6 +506,7 @@ fun ServerConnectScreen( private fun SavedServerItem( server: ServerEntry, onConnect: (ServerEntry) -> Unit, + onEdit: (ServerEntry) -> Unit, onRemove: (ServerEntry) -> Unit, ) { Row( @@ -476,6 +549,9 @@ private fun SavedServerItem( } } } + IconButton(onClick = { onEdit(server) }) { + Icon(imageVector = Icons.Default.Edit, contentDescription = stringResource(R.string.edit_server), modifier = Modifier.size(18.dp), tint = TextMuted) + } IconButton(onClick = { onRemove(server) }) { Icon(imageVector = Icons.Default.Close, contentDescription = stringResource(R.string.remove_server), modifier = Modifier.size(18.dp), tint = TextMuted) } diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml index 68305488..db51acea 100644 --- a/Android/app/src/main/res/values/strings.xml +++ b/Android/app/src/main/res/values/strings.xml @@ -28,4 +28,8 @@ Refresh Server Name (optional) My Archipelago + Edit + Edit Server + Save Changes + Cancel diff --git a/neode-ui/public/packages/archipelago-companion.apk b/neode-ui/public/packages/archipelago-companion.apk index eee04860..09e39270 100644 Binary files a/neode-ui/public/packages/archipelago-companion.apk and b/neode-ui/public/packages/archipelago-companion.apk differ