1
0
forked from GRIN/grim

tor: restructure network-privacy UI + tighten English copy

On-device build160 feedback (Android, dark + light):

- Route-through-Tor card: stack it centered so it reads as switching
  between Tor and direct. Title on top, the big switch on its own row,
  full-width caption below. The subtitle can no longer clip under the
  switch at 390px or larger font scales.
- Grow toggle_large from 64x36 to 96x54 (roughly 1.5x) for the switch.
- Grin-node status dot is now the brand yellow in every theme (new
  theme::GOBLIN_YELLOW const; theme accent inverts to dark on the yellow
  theme, so it couldn't be used here).
- Drop the onboarding "NETWORK PRIVACY" top-right kicker so the privacy
  screen shows just the back chip + title, matching the settings
  sub-pages (Advanced Privacy, Node). step_header skips an empty kicker.
- Copy (English only, pending owner sign-off): merge the two intro
  blocks into one short line and drop the "How your privacy works"
  heading; "Usernames" capitalized + "to and from name authorities";
  Tor caption shortened to "Hide your IP from relays."; shorter
  Grin-node blurb. Stale non-English values left as-is; key set
  unchanged (how_privacy / how_privacy_blurb / onboarding.privacy.kicker
  kept but unused, so i18n parity holds).
This commit is contained in:
2ro
2026-07-09 23:32:15 -04:00
parent f37788cf26
commit 8e2097da87
5 changed files with 55 additions and 59 deletions
+5 -5
View File
@@ -644,22 +644,22 @@ goblin:
delete_final: "Delete permanently"
privacy:
title: "Network privacy"
intro: "Goblin sends only a few things over the network. Here's what leaves your device, and how it stays private."
intro: "Goblin sends only a few things over the network, each sealed with end-to-end encryption so relays can't read them or link them to you."
payments: "Payments"
payments_blurb: "Every nostr message carrying a slatepack."
usernames: "usernames"
usernames_blurb: "NIP-05 name lookups to and from goblin.st."
usernames: "Usernames"
usernames_blurb: "NIP-05 name lookups to and from name authorities."
price_avatars: "Price"
price_avatars_blurb: "The live fiat rate shown next to amounts."
grin_node: "Grin node"
grin_node_blurb: "Block sync and broadcasting your transaction to the network. This is public chain data, the same for everyone, and isn't linked to your identity."
grin_node_blurb: "Syncs blocks and broadcasts your transactions. Public chain data, the same for everyone, never linked to your identity."
how_privacy: "How your privacy works"
how_privacy_blurb: "Payments and messages are sealed with authenticated encryption and delivered as private messages, so a relay can't read them or link a payment back to you."
always_direct: "Always direct"
tor_off: "Tor off"
tor_on: "Tor on"
tor_switch: "Route through Tor"
tor_switch_sub: "Send your private traffic through the Tor network to hide your IP from relays."
tor_switch_sub: "Hide your IP from relays."
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
pairing:
title: "Pairing"
+6
View File
@@ -117,6 +117,12 @@ const AVATAR_PAIRS: [(Color32, Color32); 8] = [
), // lime / dark moss
];
/// The Goblin brand yellow (#FFD60A). Unlike [`ThemeTokens::accent`] (which
/// inverts to dark on the yellow theme so it reads on a bright bg), this is the
/// same brand yellow in every theme — used for status dots that must always
/// carry the brand color, e.g. the always-direct Grin-node dot.
pub const GOBLIN_YELLOW: Color32 = Color32::from_rgb(0xFF, 0xD6, 0x0A);
pub const LIGHT: ThemeTokens = ThemeTokens {
bg: Color32::from_rgb(0xFA, 0xFA, 0xF7),
surface: Color32::from_rgb(0xFF, 0xFF, 0xFF),
+13 -13
View File
@@ -235,13 +235,15 @@ impl OnboardingContent {
self.error = None;
self.step = back;
}
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
ui.label(
RichText::new(kicker)
.font(fonts::kicker())
.color(t.text_mute),
);
});
if !kicker.is_empty() {
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
ui.label(
RichText::new(kicker)
.font(fonts::kicker())
.color(t.text_mute),
);
});
}
});
ui.add_space(18.0);
ui.label(
@@ -873,12 +875,10 @@ impl OnboardingContent {
} else {
Step::ConfirmWords
};
self.step_header(
ui,
&t!("goblin.onboarding.privacy.kicker"),
&t!("goblin.privacy.title"),
back,
);
// No top-right kicker here — the Network-privacy screen shows just the
// back chip + title, matching the settings sub-pages (Advanced Privacy,
// Node) so the two entry points read identically.
self.step_header(ui, "", &t!("goblin.privacy.title"), back);
if let Some(new_val) = super::network_privacy_panels(ui, self.tor_choice) {
self.tor_choice = new_val;
}
+26 -36
View File
@@ -70,32 +70,22 @@ pub(super) fn settings_group_colored(
/// onboarding stashes it and writes it before the service starts).
pub(super) fn network_privacy_panels(ui: &mut egui::Ui, tor_on: bool) -> Option<bool> {
let t = theme::tokens();
// One short intro block: what Goblin sends + how it stays private
// (deliberately no crypto version numbers).
ui.label(
RichText::new(t!("goblin.privacy.intro"))
.font(FontId::new(14.0, fonts::regular()))
.color(t.text_dim),
);
ui.add_space(14.0);
ui.label(
RichText::new(t!("goblin.privacy.how_privacy"))
.font(FontId::new(14.0, fonts::semibold()))
.color(t.text),
);
ui.add_space(4.0);
ui.label(
RichText::new(t!("goblin.privacy.how_privacy_blurb"))
.font(FontId::new(13.0, fonts::regular()))
.color(t.text_dim),
);
ui.add_space(16.0);
// Grin node — ALWAYS DIRECT, grey dot, full width. Public chain data, the
// same for everyone, never routed over Tor.
// Grin node — ALWAYS DIRECT, brand-yellow dot, full width. Public chain
// data, the same for everyone, never routed over Tor.
settings_group(ui, &t!("goblin.privacy.always_direct"), |ui| {
ui.set_min_width(ui.available_width());
privacy_line(
ui,
t.surface_text_mute,
theme::GOBLIN_YELLOW,
&t!("goblin.privacy.grin_node"),
&t!("goblin.privacy.grin_node_blurb"),
);
@@ -132,30 +122,30 @@ pub(super) fn network_privacy_panels(ui: &mut egui::Ui, tor_on: bool) -> Option<
});
ui.add_space(18.0);
// Large Tor switch — dormant gray when off, blueviolet when on (never
// yellow). The color state lives on the dots above; this reads on/off.
// Large Tor switch — the whole card is stacked and centered so it reads as
// "flip between Tor and direct": title on top, the big switch on its own
// row, then a full-width caption below that can never clip. The switch is
// dormant gray when off, blueviolet when on (never yellow) — the color state
// lives on the dots above; this reads on/off.
let mut toggled = None;
w::card(ui, |ui| {
ui.set_min_width(ui.available_width());
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.label(
RichText::new(t!("goblin.privacy.tor_switch"))
.font(FontId::new(15.0, fonts::semibold()))
.color(t.surface_text),
);
ui.add_space(2.0);
ui.label(
RichText::new(t!("goblin.privacy.tor_switch_sub"))
.font(FontId::new(12.5, fonts::regular()))
.color(t.surface_text_dim),
);
});
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if w::toggle_large(ui, tor_on).clicked() {
toggled = Some(!tor_on);
}
});
ui.vertical_centered(|ui| {
ui.label(
RichText::new(t!("goblin.privacy.tor_switch"))
.font(FontId::new(15.0, fonts::semibold()))
.color(t.surface_text),
);
ui.add_space(12.0);
if w::toggle_large(ui, tor_on).clicked() {
toggled = Some(!tor_on);
}
ui.add_space(12.0);
ui.label(
RichText::new(t!("goblin.privacy.tor_switch_sub"))
.font(FontId::new(12.5, fonts::regular()))
.color(t.surface_text_dim),
);
});
});
ui.add_space(12.0);
+5 -5
View File
@@ -244,15 +244,15 @@ pub fn toggle(ui: &mut Ui, on: bool) -> Response {
/// the app accent. Returns the response; the caller flips the bound state.
pub fn toggle_large(ui: &mut Ui, on: bool) -> Response {
let t = theme::tokens();
let (rect, resp) = ui.allocate_exact_size(Vec2::new(64.0, 36.0), Sense::click());
let (rect, resp) = ui.allocate_exact_size(Vec2::new(96.0, 54.0), Sense::click());
let track = if on { t.tor_purple } else { t.surface2 };
ui.painter()
.rect_filled(rect, CornerRadius::same(18), track);
let knob_r = 15.0;
.rect_filled(rect, CornerRadius::same(27), track);
let knob_r = 22.0;
let knob_x = if on {
rect.right() - knob_r - 3.0
rect.right() - knob_r - 5.0
} else {
rect.left() + knob_r + 3.0
rect.left() + knob_r + 5.0
};
let knob = if on {
Color32::WHITE