diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt index 528a9d9cb..acc7c32a1 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt @@ -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(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() }, ), ) { diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsDialog.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsDialog.kt index 76f99a4c0..214be68e9 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsDialog.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsDialog.kt @@ -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") } + }, + ) } } } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsSection.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsSection.kt index e66e15f50..33161d667 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsSection.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorSettingsSection.kt @@ -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(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") } + }, + ) + } }