fix(tor): default to Internal + Full Privacy, clickable shield, sticky dialog buttons

UX improvements based on manual testing feedback:

- Default TorType changed from OFF to INTERNAL (Tor on by default)
- Default preset changed to Full Privacy (all routing via Tor)
- Shield icon always visible in sidebar (not hidden when Off)
- Shield icon clickable — opens Settings
- TorSettingsDialog Cancel/Save buttons sticky at bottom (don't scroll away)
- HorizontalDivider above buttons for visual separation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-04-01 09:14:31 +03:00
parent 95ec190b0d
commit 4aa4d39a91
4 changed files with 139 additions and 123 deletions
@@ -37,19 +37,19 @@ object DesktopTorPreferences : ITorSettingsPersistence {
override fun load(): TorSettings = override fun load(): TorSettings =
TorSettings( TorSettings(
torType = TorType.entries.firstOrNull { it.name == prefs.get("tor_type", TorType.OFF.name) } ?: TorType.OFF, torType = TorType.entries.firstOrNull { it.name == prefs.get("tor_type", TorType.INTERNAL.name) } ?: TorType.INTERNAL,
externalSocksPort = prefs.getInt("tor_external_port", 9050), externalSocksPort = prefs.getInt("tor_external_port", 9050),
onionRelaysViaTor = prefs.getBoolean("tor_onion_relays", true), onionRelaysViaTor = prefs.getBoolean("tor_onion_relays", true),
dmRelaysViaTor = prefs.getBoolean("tor_dm_relays", false), dmRelaysViaTor = prefs.getBoolean("tor_dm_relays", true),
newRelaysViaTor = prefs.getBoolean("tor_new_relays", false), newRelaysViaTor = prefs.getBoolean("tor_new_relays", true),
trustedRelaysViaTor = prefs.getBoolean("tor_trusted_relays", false), trustedRelaysViaTor = prefs.getBoolean("tor_trusted_relays", true),
urlPreviewsViaTor = prefs.getBoolean("tor_url_previews", false), urlPreviewsViaTor = prefs.getBoolean("tor_url_previews", true),
profilePicsViaTor = prefs.getBoolean("tor_profile_pics", false), profilePicsViaTor = prefs.getBoolean("tor_profile_pics", true),
imagesViaTor = prefs.getBoolean("tor_images", false), imagesViaTor = prefs.getBoolean("tor_images", true),
videosViaTor = prefs.getBoolean("tor_videos", false), videosViaTor = prefs.getBoolean("tor_videos", true),
moneyOperationsViaTor = prefs.getBoolean("tor_money", false), moneyOperationsViaTor = prefs.getBoolean("tor_money", true),
nip05VerificationsViaTor = prefs.getBoolean("tor_nip05", false), nip05VerificationsViaTor = prefs.getBoolean("tor_nip05", true),
mediaUploadsViaTor = prefs.getBoolean("tor_media_uploads", false), mediaUploadsViaTor = prefs.getBoolean("tor_media_uploads", true),
) )
override fun save(settings: TorSettings) { override fun save(settings: TorSettings) {
@@ -90,12 +90,10 @@ fun DeckSidebar(
lastPingTimeSec = lastPingTimeSec, lastPingTimeSec = lastPingTimeSec,
) )
if (torStatus !is TorServiceStatus.Off) {
Spacer(Modifier.size(4.dp)) Spacer(Modifier.size(4.dp))
TorStatusIndicator(status = torStatus) TorStatusIndicator(status = torStatus, onClick = onOpenSettings)
}
Spacer(Modifier.size(8.dp)) Spacer(Modifier.size(4.dp))
IconButton(onClick = onOpenSettings) { IconButton(onClick = onOpenSettings) {
Icon( Icon(
@@ -77,10 +77,12 @@ fun TorSettingsDialog(
state = rememberDialogState(size = DpSize(480.dp, 640.dp)), state = rememberDialogState(size = DpSize(480.dp, 640.dp)),
) { ) {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
Column(modifier = Modifier.padding(24.dp)) {
// Scrollable content
Column( Column(
modifier = modifier =
Modifier Modifier
.padding(24.dp) .weight(1f)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
) { ) {
// Status // Status
@@ -182,9 +184,12 @@ fun TorSettingsDialog(
ToggleRow("Money operations via Tor", editSettings.moneyOperationsViaTor) { editSettings = editSettings.copy(moneyOperationsViaTor = it) } ToggleRow("Money operations via Tor", editSettings.moneyOperationsViaTor) { editSettings = editSettings.copy(moneyOperationsViaTor = it) }
ToggleRow("Media uploads via Tor", editSettings.mediaUploadsViaTor) { editSettings = editSettings.copy(mediaUploadsViaTor = it) } ToggleRow("Media uploads via Tor", editSettings.mediaUploadsViaTor) { editSettings = editSettings.copy(mediaUploadsViaTor = it) }
Spacer(Modifier.height(24.dp)) Spacer(Modifier.height(16.dp))
} // end scrollable content
// Buttons // Sticky bottom buttons — always visible
HorizontalDivider()
Spacer(Modifier.height(12.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End, horizontalArrangement = Arrangement.End,
@@ -30,6 +30,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Shield import androidx.compose.material.icons.filled.Shield
import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.Shield
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -43,12 +44,13 @@ import com.vitorpamplona.amethyst.commons.tor.TorServiceStatus
/** /**
* Small shield icon showing Tor connection status in the sidebar footer. * Small shield icon showing Tor connection status in the sidebar footer.
* Tooltip shows status text (no port number for security). * Clickable to open Tor settings. Tooltip shows status text.
*/ */
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun TorStatusIndicator( fun TorStatusIndicator(
status: TorServiceStatus, status: TorServiceStatus,
onClick: (() -> Unit)? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val (icon, tint, tooltip) = val (icon, tint, tooltip) =
@@ -86,6 +88,16 @@ fun TorStatusIndicator(
}, },
tooltipPlacement = TooltipPlacement.CursorPoint(alignment = Alignment.BottomEnd, offset = DpOffset(0.dp, 16.dp)), tooltipPlacement = TooltipPlacement.CursorPoint(alignment = Alignment.BottomEnd, offset = DpOffset(0.dp, 16.dp)),
) { ) {
if (onClick != null) {
IconButton(onClick = onClick, modifier = modifier.size(28.dp)) {
Icon(
imageVector = icon,
contentDescription = tooltip,
tint = tint,
modifier = Modifier.size(20.dp),
)
}
} else {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = tooltip, contentDescription = tooltip,
@@ -93,4 +105,5 @@ fun TorStatusIndicator(
modifier = modifier.size(20.dp), modifier = modifier.size(20.dp),
) )
} }
}
} }