feat(android): edit server entries from in-app settings menu (NESMenu); bump to 0.4.12 (vc16)

The 0.4.11 edit affordance only lived on ServerConnectScreen, which a
connected user never sees. Add edit to NESMenu — the settings modal
reached via two-finger hold while connected: a ✎ pencil on each saved
server opens the form pre-populated (Edit Server header + Cancel),
persists via ServerPreferences.updateSavedServer(), and reconnects when
the edited server is the live one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-06-26 13:08:18 +01:00
parent 389e602097
commit a90fea80ed
4 changed files with 67 additions and 7 deletions

View File

@ -11,8 +11,8 @@ android {
applicationId = "com.archipelago.app" applicationId = "com.archipelago.app"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 15 versionCode = 16
versionName = "0.4.11" versionName = "0.4.12"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true

View File

@ -75,6 +75,7 @@ fun NESMenu(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSelectServer: (ServerEntry) -> Unit, onSelectServer: (ServerEntry) -> Unit,
onAddServer: (ServerEntry) -> Unit, onAddServer: (ServerEntry) -> Unit,
onEditServer: (ServerEntry, ServerEntry) -> Unit,
onRemoveServer: (ServerEntry) -> Unit, onRemoveServer: (ServerEntry) -> Unit,
onToggleMode: () -> Unit, onToggleMode: () -> Unit,
onToggleStyle: () -> Unit, onToggleStyle: () -> Unit,
@ -87,7 +88,7 @@ fun NESMenu(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
AnimatedVisibility(visible = visible, enter = fadeIn() + scaleIn(initialScale = 0.95f), exit = fadeOut() + scaleOut(targetScale = 0.95f)) { AnimatedVisibility(visible = visible, enter = fadeIn() + scaleIn(initialScale = 0.95f), exit = fadeOut() + scaleOut(targetScale = 0.95f)) {
MenuPanel(servers, activeServer, isGamepadMode, controllerStyle, onDismiss, onSelectServer, onAddServer, onRemoveServer, onToggleMode, onToggleStyle, onBackToWebView) MenuPanel(servers, activeServer, isGamepadMode, controllerStyle, onDismiss, onSelectServer, onAddServer, onEditServer, onRemoveServer, onToggleMode, onToggleStyle, onBackToWebView)
} }
} }
} }
@ -102,21 +103,39 @@ private fun MenuPanel(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSelectServer: (ServerEntry) -> Unit, onSelectServer: (ServerEntry) -> Unit,
onAddServer: (ServerEntry) -> Unit, onAddServer: (ServerEntry) -> Unit,
onEditServer: (ServerEntry, ServerEntry) -> Unit,
onRemoveServer: (ServerEntry) -> Unit, onRemoveServer: (ServerEntry) -> Unit,
onToggleMode: () -> Unit, onToggleMode: () -> Unit,
onToggleStyle: () -> Unit, onToggleStyle: () -> Unit,
onBackToWebView: (() -> Unit)?, onBackToWebView: (() -> Unit)?,
) { ) {
var showAdd by remember { mutableStateOf(false) } var showAdd by remember { mutableStateOf(false) }
// The saved server being edited, or null when adding a new one.
var editing by remember { mutableStateOf<ServerEntry?>(null) }
var nm by remember { mutableStateOf("") } var nm by remember { mutableStateOf("") }
var addr by remember { mutableStateOf("") } var addr by remember { mutableStateOf("") }
var pwd by remember { mutableStateOf("") } var pwd by remember { mutableStateOf("") }
fun resetForm() {
nm = ""; addr = ""; pwd = ""; showAdd = false; editing = null
}
fun startEdit(server: ServerEntry) {
editing = server
nm = server.name; addr = server.address; pwd = server.password
showAdd = false
}
fun submit() { fun submit() {
if (addr.isNotBlank()) { if (addr.isBlank()) return
val orig = editing
if (orig != null) {
// Preserve fields the compact form doesn't expose (scheme, port).
onEditServer(orig, orig.copy(address = addr, password = pwd, name = nm))
} else {
onAddServer(ServerEntry(addr, false, password = pwd, name = nm)) onAddServer(ServerEntry(addr, false, password = pwd, name = nm))
nm = ""; addr = ""; pwd = ""; showAdd = false
} }
resetForm()
} }
Column( Column(
@ -149,6 +168,7 @@ private fun MenuPanel(
label = server.displayName(), label = server.displayName(),
selected = active, selected = active,
onClick = { onSelectServer(server) }, onClick = { onSelectServer(server) },
onEdit = { startEdit(server) },
onRemove = { onRemoveServer(server) }, onRemove = { onRemoveServer(server) },
) )
} }
@ -157,8 +177,8 @@ private fun MenuPanel(
Text("No servers", color = TextMuted, fontSize = 14.sp, modifier = Modifier.padding(vertical = 4.dp)) Text("No servers", color = TextMuted, fontSize = 14.sp, modifier = Modifier.padding(vertical = 4.dp))
} }
// Add server // Add / edit server
if (showAdd) { if (showAdd || editing != null) {
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@ -168,6 +188,25 @@ private fun MenuPanel(
.padding(12.dp), .padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
if (editing != null) "Edit Server" else "Add Server",
color = TextMuted,
fontSize = 13.sp,
letterSpacing = 1.sp,
fontWeight = FontWeight.Medium,
)
Text(
"Cancel",
color = TextMuted,
fontSize = 13.sp,
modifier = Modifier.clickable { resetForm() }.padding(start = 8.dp),
)
}
GlassField( GlassField(
value = nm, onValueChange = { nm = it }, value = nm, onValueChange = { nm = it },
placeholder = "Name (optional)", placeholder = "Name (optional)",
@ -228,6 +267,7 @@ private fun MenuItem(
selected: Boolean = false, selected: Boolean = false,
labelColor: Color = TextPrimary, labelColor: Color = TextPrimary,
onClick: () -> Unit, onClick: () -> Unit,
onEdit: (() -> Unit)? = null,
onRemove: (() -> Unit)? = null, onRemove: (() -> Unit)? = null,
) { ) {
Row( Row(
@ -247,7 +287,16 @@ private fun MenuItem(
color = if (selected) BitcoinOrange else labelColor, color = if (selected) BitcoinOrange else labelColor,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
modifier = Modifier.weight(1f),
) )
if (onEdit != null) {
Text(
"",
color = TextMuted,
fontSize = 16.sp,
modifier = Modifier.clickable { onEdit() }.padding(horizontal = 8.dp),
)
}
if (onRemove != null) { if (onRemove != null) {
Text( Text(
"", "",

View File

@ -216,6 +216,17 @@ fun RemoteInputScreen(onBack: () -> Unit) {
onAddServer = { server -> onAddServer = { server ->
scope.launch { prefs.addSavedServer(server); if (activeServer == null) prefs.setActiveServer(server) } scope.launch { prefs.addSavedServer(server); if (activeServer == null) prefs.setActiveServer(server) }
}, },
onEditServer = { original, updated ->
scope.launch {
prefs.updateSavedServer(original, updated)
// If the edited server is the live one, reconnect with the new
// address/credentials so the change takes effect immediately.
if (original.serialize() == activeServer?.serialize()) {
ws.disconnect()
prefs.setActiveServer(updated)
}
}
},
onRemoveServer = { server -> onRemoveServer = { server ->
scope.launch { scope.launch {
prefs.removeSavedServer(server) prefs.removeSavedServer(server)