diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt
index 5190d02c82..afd68f28f2 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt
@@ -113,6 +113,7 @@ import com.vitorpamplona.amethyst.commons.resources.drawer_section_you
import com.vitorpamplona.amethyst.commons.resources.followers
import com.vitorpamplona.amethyst.commons.resources.following
import com.vitorpamplona.amethyst.commons.resources.ic_qrcode
+import com.vitorpamplona.amethyst.commons.resources.ic_tor
import com.vitorpamplona.amethyst.commons.resources.longs
import com.vitorpamplona.amethyst.commons.resources.pictures
import com.vitorpamplona.amethyst.commons.resources.profile
@@ -124,6 +125,8 @@ import com.vitorpamplona.amethyst.commons.resources.route_chess
import com.vitorpamplona.amethyst.commons.resources.share_hls_video
import com.vitorpamplona.amethyst.commons.resources.show_npub_as_a_qr_code
import com.vitorpamplona.amethyst.commons.resources.status_update
+import com.vitorpamplona.amethyst.commons.resources.tor_splash_connecting
+import com.vitorpamplona.amethyst.commons.resources.tor_status_connected
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStatus
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.commons.ui.layouts.PermanentDrawerWidth
@@ -163,6 +166,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
import com.vitorpamplona.amethyst.isDebug
import com.vitorpamplona.amethyst.ui.painterRes
+import com.vitorpamplona.amethyst.ui.tor.TorServiceStatus
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip38UserStatus.UserStatusEvent
@@ -1078,32 +1082,41 @@ fun BottomContent(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Absolute.SpaceBetween,
) {
- val string =
- remember {
- buildAnnotatedString {
- withLink(
- LinkAnnotation.Clickable(
- "clickable",
- TextStyleBottomNavBar,
+ // The version (release notes link) and, while Tor is in use, its status beside it.
+ Row(
+ modifier = Modifier.weight(1f, fill = false),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val string =
+ remember {
+ buildAnnotatedString {
+ withLink(
+ LinkAnnotation.Clickable(
+ "clickable",
+ TextStyleBottomNavBar,
+ ) {
+ nav.nav(Route.Note(BuildConfig.RELEASE_NOTES_ID))
+ nav.closeDrawer()
+ },
) {
- nav.nav(Route.Note(BuildConfig.RELEASE_NOTES_ID))
- nav.closeDrawer()
- },
- ) {
- append("v")
- append(BuildConfig.VERSION_NAME)
- append("-")
- append(BuildConfig.FLAVOR.uppercase())
+ append("v")
+ append(BuildConfig.VERSION_NAME)
+ append("-")
+ append(BuildConfig.FLAVOR.uppercase())
+ }
}
}
- }
- Text(
- text = string,
- modifier = Modifier.padding(start = 16.dp),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
+ Text(
+ text = string,
+ // Yields to the Tor icon on a narrow drawer instead of pushing it off.
+ modifier = Modifier.weight(1f, fill = false).padding(start = 16.dp),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+
+ TorStatusIcon(nav)
+ }
IconButton(
onClick = {
@@ -1122,6 +1135,34 @@ fun BottomContent(
}
}
+/**
+ * Tor's state next to the app version, opening the Tor settings on tap. Hidden while Tor is off: that
+ * is a choice, not a fault, so it gets no icon at all rather than an alarming one. Connected only once
+ * the directory is ready ([TorServiceStatus.isFullyBootstrapped]); a bound proxy still downloading it
+ * cannot carry traffic yet, so it reads as connecting.
+ */
+@Composable
+private fun TorStatusIcon(nav: INav) {
+ val torStatus by Amethyst.instance.torManager.status
+ .collectAsStateWithLifecycle()
+ if (torStatus is TorServiceStatus.Off) return
+
+ val connected = torStatus.isFullyBootstrapped
+ IconButton(
+ onClick = {
+ nav.nav(Route.PrivacyOptions)
+ nav.closeDrawer()
+ },
+ ) {
+ Icon(
+ painter = painterRes(Res.drawable.ic_tor, 2),
+ contentDescription = stringRes(if (connected) Res.string.tor_status_connected else Res.string.tor_splash_connecting),
+ modifier = Size20Modifier,
+ tint = if (connected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+}
+
@Preview
@Composable
private fun CollapsibleSectionPreview() {
diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml
index 99b2ba83b9..35ee7b63ac 100644
--- a/commonsUI/src/commonMain/composeResources/values/strings.xml
+++ b/commonsUI/src/commonMain/composeResources/values/strings.xml
@@ -1947,6 +1947,7 @@
Use regular connection
Keep waiting
Connecting to Tor…
+ Connected to Tor
Tor error: %1$s
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.