feat(tor): restart confirmation dialog + key() app rebuild on Tor toggle
When user changes Tor settings, shows "Restart Required" confirmation dialog. On confirm: saves prefs, triggers appRestartKey++ which forces Compose to unmount/remount entire App tree. Key behaviors: - Window stays open, user stays logged in (AccountManager outside App) - Column layout preserved (DeckState outside App) - DisposableEffect stops old Tor daemon before new one starts - TorSettingsSection mode change → confirmation dialog - TorSettingsDialog Save → confirmation dialog - Relays, subscriptions, caches all recreated fresh Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,7 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -184,6 +185,7 @@ fun main() {
|
||||
height = 800.dp,
|
||||
position = WindowPosition.Aligned(Alignment.Center),
|
||||
)
|
||||
var appRestartKey by remember { mutableStateOf(0) }
|
||||
var showComposeDialog by remember { mutableStateOf(false) }
|
||||
var replyToNote by remember { mutableStateOf<com.vitorpamplona.quartz.nip01Core.core.Event?>(null) }
|
||||
val deckScope = rememberCoroutineScope()
|
||||
@@ -410,25 +412,28 @@ fun main() {
|
||||
LocalAwtWindow provides window,
|
||||
LocalIsImmersiveFullscreen provides immersiveFullscreenState,
|
||||
) {
|
||||
App(
|
||||
layoutMode = layoutMode,
|
||||
deckState = deckState,
|
||||
accountManager = accountManager,
|
||||
showComposeDialog = showComposeDialog,
|
||||
showAddColumnDialog = showAddColumnDialog,
|
||||
onShowComposeDialog = { showComposeDialog = true },
|
||||
onShowReplyDialog = { event ->
|
||||
replyToNote = event
|
||||
showComposeDialog = true
|
||||
},
|
||||
onDismissComposeDialog = {
|
||||
showComposeDialog = false
|
||||
replyToNote = null
|
||||
},
|
||||
onDismissAddColumnDialog = { showAddColumnDialog = false },
|
||||
onShowAddColumnDialog = { showAddColumnDialog = true },
|
||||
replyToNote = replyToNote,
|
||||
)
|
||||
key(appRestartKey) {
|
||||
App(
|
||||
layoutMode = layoutMode,
|
||||
deckState = deckState,
|
||||
accountManager = accountManager,
|
||||
showComposeDialog = showComposeDialog,
|
||||
showAddColumnDialog = showAddColumnDialog,
|
||||
onShowComposeDialog = { showComposeDialog = true },
|
||||
onShowReplyDialog = { event ->
|
||||
replyToNote = event
|
||||
showComposeDialog = true
|
||||
},
|
||||
onDismissComposeDialog = {
|
||||
showComposeDialog = false
|
||||
replyToNote = null
|
||||
},
|
||||
onDismissAddColumnDialog = { showAddColumnDialog = false },
|
||||
onShowAddColumnDialog = { showAddColumnDialog = true },
|
||||
replyToNote = replyToNote,
|
||||
onRestartApp = { appRestartKey++ },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,6 +452,7 @@ fun App(
|
||||
onDismissAddColumnDialog: () -> Unit,
|
||||
onShowAddColumnDialog: () -> Unit,
|
||||
replyToNote: com.vitorpamplona.quartz.nip01Core.core.Event?,
|
||||
onRestartApp: () -> Unit = {},
|
||||
) {
|
||||
val localCache = remember { DesktopLocalCache() }
|
||||
val accountState by accountManager.accountState.collectAsState()
|
||||
@@ -468,6 +474,14 @@ fun App(
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up Tor daemon on unmount (key() change triggers this)
|
||||
DisposableEffect(torManager) {
|
||||
onDispose {
|
||||
torManager.stopSync()
|
||||
activeTorManager = null
|
||||
}
|
||||
}
|
||||
|
||||
// Build TorRelayEvaluation for per-relay routing
|
||||
val torRelayEvaluation =
|
||||
remember(torSettings) {
|
||||
@@ -606,6 +620,8 @@ fun App(
|
||||
.save(newSettings)
|
||||
torTypeFlow.value = newSettings.torType
|
||||
externalPortFlow.value = newSettings.externalSocksPort
|
||||
// Rebuild app to apply Tor changes
|
||||
onRestartApp()
|
||||
},
|
||||
),
|
||||
) {
|
||||
|
||||
+20
-4
@@ -71,6 +71,8 @@ fun TorSettingsDialog(
|
||||
) {
|
||||
var editSettings by remember { mutableStateOf(currentSettings) }
|
||||
|
||||
var showRestartConfirm by remember { mutableStateOf(false) }
|
||||
|
||||
DialogWindow(
|
||||
onCloseRequest = onDismiss,
|
||||
title = "Tor Settings",
|
||||
@@ -196,10 +198,24 @@ fun TorSettingsDialog(
|
||||
) {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
TextButton(onClick = {
|
||||
onSettingsChanged(editSettings)
|
||||
onDismiss()
|
||||
}) { Text("Save") }
|
||||
TextButton(onClick = { showRestartConfirm = true }) { Text("Save") }
|
||||
}
|
||||
|
||||
if (showRestartConfirm) {
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = { showRestartConfirm = false },
|
||||
title = { Text("Restart Required") },
|
||||
text = { Text("Tor changes require restarting. Proceed?") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onSettingsChanged(editSettings)
|
||||
onDismiss()
|
||||
}) { Text("Restart") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showRestartConfirm = false }) { Text("Cancel") }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
@@ -58,6 +59,7 @@ fun TorSettingsSection(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
var pendingSettings by remember { mutableStateOf<TorSettings?>(null) }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
@@ -94,7 +96,7 @@ fun TorSettingsSection(
|
||||
TorType.entries.forEachIndexed { index, torType ->
|
||||
SegmentedButton(
|
||||
shape = SegmentedButtonDefaults.itemShape(index = index, count = TorType.entries.size),
|
||||
onClick = { onSettingsChanged(currentSettings.copy(torType = torType)) },
|
||||
onClick = { pendingSettings = currentSettings.copy(torType = torType) },
|
||||
selected = currentSettings.torType == torType,
|
||||
) {
|
||||
Text(torType.name.lowercase().replaceFirstChar { it.uppercase() })
|
||||
@@ -136,4 +138,22 @@ fun TorSettingsSection(
|
||||
onDismiss = { showDialog = false },
|
||||
)
|
||||
}
|
||||
|
||||
// Restart confirmation dialog
|
||||
pendingSettings?.let { settings ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { pendingSettings = null },
|
||||
title = { Text("Restart Required") },
|
||||
text = { Text("Changing Tor mode requires restarting. Your session will be briefly interrupted.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onSettingsChanged(settings)
|
||||
pendingSettings = null
|
||||
}) { Text("Restart") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { pendingSettings = null }) { Text("Cancel") }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user