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:
+5
-5
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user