diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml
index 8a227b5f8f..c98506f31e 100644
--- a/commonsUI/src/commonMain/composeResources/values/strings.xml
+++ b/commonsUI/src/commonMain/composeResources/values/strings.xml
@@ -1865,8 +1865,7 @@
Keep waiting
Connecting to Tor…
Tor error: %1$s
- Amethyst sends your connections through Tor to hide your IP address. Starting Tor can take a minute and needs internet access.
- Tor settings
+ Amethyst uses Tor to hide your IP address from relays. Starting Tor can take a minute and needs internet access.
Tap a category to open Android notification settings for it — sound, importance, badges and Do Not Disturb live there.
On
Silent
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 b4cd746979..88859f79c6 100644
--- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt
+++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt
@@ -347,14 +347,11 @@ fun main(args: Array) {
val feedSearchActiveState = remember { mutableStateOf(false) }
// Tor state at Window level — survives key() app rebuild
- var torSettings by remember {
- mutableStateOf(
- com.vitorpamplona.amethyst.desktop.tor.DesktopTorPreferences
- .load(),
- )
- }
+ val torSettings = remember { DesktopTorPreferences.load() }
val torTypeFlow = remember { kotlinx.coroutines.flow.MutableStateFlow(torSettings.torType) }
val externalPortFlow = remember { kotlinx.coroutines.flow.MutableStateFlow(torSettings.externalSocksPort) }
+ // "Use regular connection" on the Tor splash: Tor off for this run only, never saved.
+ val torSessionBypassFlow = remember { kotlinx.coroutines.flow.MutableStateFlow(false) }
val windowScope = rememberCoroutineScope()
val torManager =
remember {
@@ -739,6 +736,7 @@ fun main(args: Array) {
torManager = torManager,
torTypeFlow = torTypeFlow,
externalPortFlow = externalPortFlow,
+ torSessionBypassFlow = torSessionBypassFlow,
initialTorSettings = torSettings,
onNavigateToScreen = { navigateToScreen = it },
)
@@ -775,6 +773,7 @@ fun App(
torManager: com.vitorpamplona.amethyst.commons.tor.ITorManager,
torTypeFlow: kotlinx.coroutines.flow.MutableStateFlow,
externalPortFlow: kotlinx.coroutines.flow.MutableStateFlow,
+ torSessionBypassFlow: kotlinx.coroutines.flow.MutableStateFlow,
initialTorSettings: com.vitorpamplona.amethyst.commons.tor.TorSettings,
onNavigateToScreen: ((DeckColumnType) -> Unit) -> Unit = {},
testOverrides: LaunchTestOverrides? = null,
@@ -831,6 +830,7 @@ fun App(
torManager = torManager,
torTypeFlow = torTypeFlow,
externalPortFlow = externalPortFlow,
+ torSessionBypassFlow = torSessionBypassFlow,
initialTorSettings = initialTorSettings,
onNavigateToScreen = onNavigateToScreen,
testOverrides = testOverrides,
@@ -866,6 +866,7 @@ private fun AppInner(
torManager: com.vitorpamplona.amethyst.commons.tor.ITorManager,
torTypeFlow: kotlinx.coroutines.flow.MutableStateFlow,
externalPortFlow: kotlinx.coroutines.flow.MutableStateFlow,
+ torSessionBypassFlow: kotlinx.coroutines.flow.MutableStateFlow,
initialTorSettings: com.vitorpamplona.amethyst.commons.tor.TorSettings,
onNavigateToScreen: ((DeckColumnType) -> Unit) -> Unit,
testOverrides: LaunchTestOverrides?,
@@ -878,28 +879,26 @@ private fun AppInner(
}
// Always reload from prefs — after key() rebuild, prefs have the latest saved settings.
- // The mode comes from the window-level torTypeFlow instead, so a session-only "continue
- // without Tor" (which never touches prefs) survives the rebuild.
// Tests can short-circuit the prefs read via `testOverrides.torSettingsOverride` so the
// Tor splash gate (below) does not block them behind a real kmp-tor runtime.
+ // torSettings is the user's saved choice (what the settings UI shows and saves);
+ // effectiveTorSettings is what this session actually routes by.
var torSettings by remember {
- mutableStateOf(
- testOverrides?.torSettingsOverride
- ?: DesktopTorPreferences.load().copy(torType = torTypeFlow.value),
- )
+ mutableStateOf(testOverrides?.torSettingsOverride ?: DesktopTorPreferences.load())
}
+ val torSessionBypass by torSessionBypassFlow.collectAsState()
+ val effectiveTorSettings = if (torSessionBypass) torSettings.copy(torType = TorType.OFF) else torSettings
// Gate: block EVERYTHING until Tor proxy is ready (when Tor expected)
// This must be before any OkHttpClient/Coil/relay creation
val torStatus by torManager.status.collectAsState()
- val isTorExpected = torSettings.torType != TorType.OFF
+ val isTorExpected = effectiveTorSettings.torType != TorType.OFF
if (isTorExpected && torStatus !is TorServiceStatus.Active) {
var showTorSettings by remember { mutableStateOf(false) }
TorConnectingSplash(
status = torStatus,
onContinueWithoutTor = {
- // Session only: prefs keep the user's Tor choice for the next launch.
- torSettings = torSettings.copy(torType = TorType.OFF)
+ torSessionBypassFlow.value = true
torTypeFlow.value = TorType.OFF
},
onOpenTorSettings = { showTorSettings = true },
@@ -909,11 +908,12 @@ private fun AppInner(
currentSettings = torSettings,
torStatus = torStatus,
onSettingsChanged = { newSettings ->
+ // No onRestartApp(): nothing below the gate has been built yet, and a
+ // rebuild would only restart the splash (and its escape timer).
torSettings = newSettings
DesktopTorPreferences.save(newSettings)
torTypeFlow.value = newSettings.torType
externalPortFlow.value = newSettings.externalSocksPort
- onRestartApp()
},
onDismiss = { showTorSettings = false },
)
@@ -977,15 +977,15 @@ private fun AppInner(
// Build TorRelayEvaluation for per-relay routing
val torRelayEvaluation =
- remember(torSettings) {
+ remember(effectiveTorSettings) {
com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation(
torSettings =
com.vitorpamplona.amethyst.commons.tor.TorRelaySettings(
- torType = torSettings.torType,
- onionRelaysViaTor = torSettings.onionRelaysViaTor,
- dmRelaysViaTor = torSettings.dmRelaysViaTor,
- newRelaysViaTor = torSettings.newRelaysViaTor,
- trustedRelaysViaTor = torSettings.trustedRelaysViaTor,
+ torType = effectiveTorSettings.torType,
+ onionRelaysViaTor = effectiveTorSettings.onionRelaysViaTor,
+ dmRelaysViaTor = effectiveTorSettings.dmRelaysViaTor,
+ newRelaysViaTor = effectiveTorSettings.newRelaysViaTor,
+ trustedRelaysViaTor = effectiveTorSettings.trustedRelaysViaTor,
),
// TODO: populate from account relay lists
classification =
@@ -1491,9 +1491,11 @@ private fun AppInner(
status = currentTorStatus,
settings = torSettings,
onSettingsChanged = { newSettings ->
+ // Saving re-applies the saved choice, ending a
+ // session bypass from the splash.
+ torSessionBypassFlow.value = false
torSettings = newSettings
- com.vitorpamplona.amethyst.desktop.tor.DesktopTorPreferences
- .save(newSettings)
+ DesktopTorPreferences.save(newSettings)
torTypeFlow.value = newSettings.torType
externalPortFlow.value = newSettings.externalSocksPort
// Rebuild app to apply Tor changes
diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorConnectingSplash.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorConnectingSplash.kt
index 44313c4817..d6b93b9b90 100644
--- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorConnectingSplash.kt
+++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/tor/TorConnectingSplash.kt
@@ -47,11 +47,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.resources.Res
+import com.vitorpamplona.amethyst.commons.resources.app_logo
+import com.vitorpamplona.amethyst.commons.resources.connect_via_tor2
import com.vitorpamplona.amethyst.commons.resources.tor_continue_without_for_session
import com.vitorpamplona.amethyst.commons.resources.tor_splash_connecting
import com.vitorpamplona.amethyst.commons.resources.tor_splash_error
import com.vitorpamplona.amethyst.commons.resources.tor_splash_explainer
-import com.vitorpamplona.amethyst.commons.resources.tor_splash_settings
import com.vitorpamplona.amethyst.commons.tor.TorServiceStatus
import com.vitorpamplona.amethyst.desktop.platform.IconResources
import kotlinx.coroutines.delay
@@ -112,7 +113,7 @@ fun TorConnectingSplash(
Spacer(Modifier.height(24.dp))
Icon(
painter = IconResources.rawBitmapPainter,
- contentDescription = "Amethyst",
+ contentDescription = stringResource(Res.string.app_logo),
modifier = Modifier.size(96.dp),
tint = MaterialTheme.colorScheme.primary,
)
@@ -120,7 +121,7 @@ fun TorConnectingSplash(
Spacer(Modifier.height(24.dp))
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
OutlinedButton(onClick = onOpenTorSettings) {
- Text(stringResource(Res.string.tor_splash_settings))
+ Text(stringResource(Res.string.connect_via_tor2))
}
Button(onClick = onContinueWithoutTor) {
Text(stringResource(Res.string.tor_continue_without_for_session))
diff --git a/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/ui/AppStateMachineTest.kt b/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/ui/AppStateMachineTest.kt
index cd98c7ea9c..8db43c0fd2 100644
--- a/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/ui/AppStateMachineTest.kt
+++ b/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/ui/AppStateMachineTest.kt
@@ -129,6 +129,7 @@ class AppStateMachineTest {
torManager = torManager,
torTypeFlow = MutableStateFlow(TorType.OFF),
externalPortFlow = MutableStateFlow(9050),
+ torSessionBypassFlow = MutableStateFlow(false),
initialTorSettings = OFF_TOR_SETTINGS,
testOverrides =
LaunchTestOverrides(
@@ -190,6 +191,7 @@ class AppStateMachineTest {
torManager = torManager,
torTypeFlow = MutableStateFlow(TorType.OFF),
externalPortFlow = MutableStateFlow(9050),
+ torSessionBypassFlow = MutableStateFlow(false),
initialTorSettings = OFF_TOR_SETTINGS,
testOverrides =
LaunchTestOverrides(
@@ -266,6 +268,7 @@ class AppStateMachineTest {
torManager = torManager,
torTypeFlow = MutableStateFlow(TorType.OFF),
externalPortFlow = MutableStateFlow(9050),
+ torSessionBypassFlow = MutableStateFlow(false),
initialTorSettings = OFF_TOR_SETTINGS,
testOverrides =
LaunchTestOverrides(
@@ -342,6 +345,7 @@ class AppStateMachineTest {
torManager = torManager,
torTypeFlow = MutableStateFlow(TorType.OFF),
externalPortFlow = MutableStateFlow(9050),
+ torSessionBypassFlow = MutableStateFlow(false),
initialTorSettings = OFF_TOR_SETTINGS,
testOverrides =
LaunchTestOverrides(