tor: network-privacy UI, blueviolet token, big switch + status wiring
Phase 2 slice 3 of the per-user-tor plan: the interactive UX layer on top of slices 1-2 (wallet-level Tor setting + transport-aware relay selection). Colors: - Add a tor_purple token (CSS blueviolet #8A2BE2) to ThemeTokens in all three theme consts, exposed via Colors::tor_purple(). Shared Network-privacy component (onboarding + settings never drift): - network_privacy_panels(): intro copy (what Goblin sends + how privacy works, no crypto version numbers), a Grin-node ALWAYS-DIRECT panel (grey dot, full width), a private-traffic panel whose header is TOR OFF (red) / TOR ON (green) and whose status dots are goblin-yellow when off / blueviolet when on, a large Tor switch (w::toggle_large: dormant gray off, blueviolet on, never yellow), and a VPN nudge. Repurposes the old read-only privacy_ui. Onboarding: - New Step::Privacy rendered before wallet creation; the create/restore triggers route here first. New users default OFF. The choice is stashed on OnboardingContent and written as the new wallet's EXPLICIT tor setting (set_tor_enabled) right after open() (init_nostr builds the service synchronously; start() runs later on the sync thread), so a new wallet writes an explicit value while upgraders keep None -> Tor ON. Settings: - Tor routing row shows On/Off and opens the Network-privacy screen; the big switch there flips the wallet tor setting (set_tor_enabled + set_route_over_tor) and restarts the service so the pool rebuilds on the new transport. Status lines: - Rewire the 3 Tor-only status call sites (home card, Me identity row, onboarding identity) to service.transport_status(), adding Connected/ Connecting (direct) states so a clearnet wallet no longer reads as forever connecting over Tor. Fix the Me-row repaint loop to gate on relay-connected rather than the Tor-only transport_ready. - Re-export TransportStatus from crate::nostr (slice-1 miss). i18n: 16 new keys added to all 10 locales (English placeholder in the 9 non-English files; a following slice translates them).
This commit is contained in:
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Über Tor verbunden"
|
||||
tor_ready: "Tor bereit · Relays…"
|
||||
connecting_tor: "Verbinde mit Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "Node nicht erreichbar"
|
||||
node_synced: "Node synchronisiert"
|
||||
syncing: "Synchronisiere…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "Privatsphäre"
|
||||
tor_routing: "Tor-Routing"
|
||||
messages_lookups: "Nachrichten & Abfragen"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "Automatisch annehmen"
|
||||
pairing: "Preiswährung"
|
||||
accept_anyone: "Jeder"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "Direkte Verbindung"
|
||||
grin_node: "Grin-Node"
|
||||
grin_node_blurb: "Block-Synchronisierung und Übertragung deiner Transaktion ins Netzwerk. Dies sind öffentliche Chain-Daten, für alle gleich, und nicht mit deiner Identität verknüpft."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "Kopplung"
|
||||
intro: "Womit dein Guthaben und deine Beträge verglichen werden."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "Dein Wallet-Passwort verschlüsselt und entsperrt jede Identität. Gib es ein, um fortzufahren."
|
||||
working: "Arbeite…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "Privates Geld"
|
||||
private_money_body: "Goblin ist ein Wallet für grin — digitales Bargeld ohne Beträge oder Adressen auf seiner Chain."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "Schlüssel wird erstellt…"
|
||||
connected_tor: "über Tor verbunden"
|
||||
connecting_tor: "verbinde über Tor…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "Ein Zahlungsschlüssel, der nicht Teil deines Seeds ist — jederzeit rotierbar, ohne deine Mittel zu berühren."
|
||||
restoring: "Deine Identitäten werden wiederhergestellt…"
|
||||
clean_slate_blurb: "Lust auf einen Neuanfang? Tausche jederzeit einen brandneuen Schlüssel ein — das neue Du ist nicht mit dem alten verknüpft. Gleiches Wallet, frisches Gesicht."
|
||||
|
||||
+18
-1
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Connected over Tor"
|
||||
tor_ready: "Tor ready · relays…"
|
||||
connecting_tor: "Connecting to Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "Can't reach node"
|
||||
node_synced: "Node synced"
|
||||
syncing: "Syncing…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "Privacy"
|
||||
tor_routing: "Tor routing"
|
||||
messages_lookups: "Messages & lookups"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "Auto-accept"
|
||||
pairing: "Price currency"
|
||||
accept_anyone: "Anyone"
|
||||
@@ -640,7 +644,7 @@ goblin:
|
||||
delete_final: "Delete permanently"
|
||||
privacy:
|
||||
title: "Network privacy"
|
||||
intro: "Goblin sends its private traffic through Tor, which hides your IP from the relay — encryption hides the rest, so a relay can't link a payment back to you."
|
||||
intro: "Goblin sends only a few things over the network. Here's what leaves your device, and how it stays private."
|
||||
payments: "Payments"
|
||||
payments_blurb: "Every nostr message carrying a slatepack."
|
||||
usernames: "usernames"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "Direct connection"
|
||||
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."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "Pairing"
|
||||
intro: "What your balance and amounts are shown against."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "Your wallet password encrypts and unlocks each identity. Enter it to continue."
|
||||
working: "Working…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "Private money"
|
||||
private_money_body: "Goblin is a wallet for grin — digital cash with no amounts or addresses on its chain."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "key being made…"
|
||||
connected_tor: "connected over Tor"
|
||||
connecting_tor: "connecting over Tor…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "A payment key that isn't part of your seed — rotate it anytime to stay private, without touching your funds."
|
||||
restoring: "Restoring your identities…"
|
||||
clean_slate_blurb: "Want a clean slate? Swap in a brand-new key any time — the new you isn't linked to the old one. Same wallet, fresh face."
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Conectado por Tor"
|
||||
tor_ready: "Tor listo · relés…"
|
||||
connecting_tor: "Conectando a Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "No se puede conectar con el nodo"
|
||||
node_synced: "Nodo sincronizado"
|
||||
syncing: "Sincronizando…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "Privacidad"
|
||||
tor_routing: "Enrutamiento por Tor"
|
||||
messages_lookups: "Mensajes y consultas"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "Aceptación automática"
|
||||
pairing: "Moneda de referencia"
|
||||
accept_anyone: "Cualquiera"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "Conexión directa"
|
||||
grin_node: "Nodo de Grin"
|
||||
grin_node_blurb: "Sincronización de bloques y transmisión de tu transacción a la red. Estos son datos públicos de la cadena, iguales para todos, y no están vinculados a tu identidad."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "Referencia de precio"
|
||||
intro: "Aquello contra lo que se muestran tu saldo y tus montos."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "Tu contraseña de billetera cifra y desbloquea cada identidad. Ingrésala para continuar."
|
||||
working: "Procesando…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "Dinero privado"
|
||||
private_money_body: "Goblin es una billetera para grin — dinero digital sin montos ni direcciones en su cadena."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "creando clave…"
|
||||
connected_tor: "conectado por Tor"
|
||||
connecting_tor: "conectando por Tor…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "Una clave de pago que no forma parte de tu frase de recuperación — rótala cuando quieras para mantener la privacidad, sin tocar tus fondos."
|
||||
restoring: "Restaurando tus identidades…"
|
||||
clean_slate_blurb: "¿Quieres empezar de cero? Cambia a una clave totalmente nueva cuando quieras — el nuevo tú no está vinculado al anterior. Misma billetera, cara nueva."
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Connecté via Tor"
|
||||
tor_ready: "Tor prêt · relais…"
|
||||
connecting_tor: "Connexion à Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "Nœud injoignable"
|
||||
node_synced: "Nœud synchronisé"
|
||||
syncing: "Synchronisation…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "Confidentialité"
|
||||
tor_routing: "Routage par Tor"
|
||||
messages_lookups: "Messages et recherches"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "Acceptation auto"
|
||||
pairing: "Devise des prix"
|
||||
accept_anyone: "Tout le monde"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "Connexion directe"
|
||||
grin_node: "Nœud grin"
|
||||
grin_node_blurb: "Synchronisation des blocs et diffusion de votre transaction sur le réseau. Ce sont des données de chaîne publiques, identiques pour tous, et non liées à votre identité."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "Appairage"
|
||||
intro: "Ce à quoi votre solde et vos montants sont comparés."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "Le mot de passe de votre portefeuille chiffre et déverrouille chaque identité. Saisissez-le pour continuer."
|
||||
working: "En cours…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "Argent privé"
|
||||
private_money_body: "Goblin est un portefeuille pour grin — de l'argent numérique sans montants ni adresses sur sa chaîne."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "clé en cours de création…"
|
||||
connected_tor: "connecté via Tor"
|
||||
connecting_tor: "connexion via Tor…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "Une clé de paiement qui ne fait pas partie de votre seed — renouvelable à tout moment, sans toucher à vos fonds."
|
||||
restoring: "Restauration de vos identités…"
|
||||
clean_slate_blurb: "Envie de repartir à zéro ? Remplacez par une toute nouvelle clé à tout moment — le nouveau vous n'est pas lié à l'ancien. Même portefeuille, nouveau visage."
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Tor経由で接続済み"
|
||||
tor_ready: "Tor準備完了 · リレー…"
|
||||
connecting_tor: "Torに接続中…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "ノードに接続できません"
|
||||
node_synced: "ノード同期済み"
|
||||
syncing: "同期中…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "プライバシー"
|
||||
tor_routing: "Torルーティング"
|
||||
messages_lookups: "メッセージと検索"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "自動承認"
|
||||
pairing: "価格通貨"
|
||||
accept_anyone: "誰でも"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "直接接続"
|
||||
grin_node: "Grinノード"
|
||||
grin_node_blurb: "ブロックの同期と、取引をネットワークにブロードキャストする処理です。これは誰にとっても同じ公開されたチェーンデータであり、あなたのアイデンティティとは結び付いていません。"
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "価格通貨"
|
||||
intro: "残高や金額を表示する際の基準となる通貨です。"
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "ウォレットのパスワードで各アイデンティティが暗号化・解除されます。続けるには入力してください。"
|
||||
working: "処理中…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "プライベートなお金"
|
||||
private_money_body: "Goblinはgrin用のウォレットです — チェーン上に金額もアドレスも残らないデジタルキャッシュです。"
|
||||
@@ -858,6 +873,8 @@ goblin:
|
||||
key_being_made: "キーを作成中…"
|
||||
connected_tor: "Tor経由で接続済み"
|
||||
connecting_tor: "Tor経由で接続中…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "リカバリーフレーズの一部ではない支払い用のキーです — 資金に影響を与えることなく、いつでもローテーションしてプライバシーを保てます。"
|
||||
restoring: "アイデンティティを復元しています…"
|
||||
clean_slate_blurb: "まっさらな状態にしたいですか? いつでもまったく新しいキーに切り替えられます — 新しいあなたは古いあなたと結び付きません。同じウォレットで、新しい顔になります。"
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Tor로 연결됨"
|
||||
tor_ready: "Tor 준비 완료 · 릴레이…"
|
||||
connecting_tor: "Tor에 연결하는 중…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "노드에 연결할 수 없음"
|
||||
node_synced: "노드 동기화됨"
|
||||
syncing: "동기화하는 중…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "프라이버시"
|
||||
tor_routing: "Tor 라우팅"
|
||||
messages_lookups: "메시지 및 조회"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "자동 수락"
|
||||
pairing: "기준 통화"
|
||||
accept_anyone: "누구나"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "직접 연결"
|
||||
grin_node: "Grin 노드"
|
||||
grin_node_blurb: "블록 동기화와 거래를 네트워크에 전파하는 과정입니다. 이는 모두에게 동일한 공개 체인 데이터이며 신원과 연결되지 않습니다."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "기준 통화"
|
||||
intro: "내 잔액과 금액이 어떤 통화로 표시되는지입니다."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "지갑 비밀번호로 각 신원을 암호화하고 잠금 해제합니다. 계속하려면 입력하세요."
|
||||
working: "처리하는 중…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "프라이빗한 돈"
|
||||
private_money_body: "Goblin은 grin을 위한 지갑입니다 — 체인에 금액도 주소도 남지 않는 디지털 현금입니다."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "키를 만드는 중…"
|
||||
connected_tor: "Tor로 연결됨"
|
||||
connecting_tor: "Tor로 연결하는 중…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "복구 문구에 속하지 않는 결제 키입니다 — 자금은 그대로 둔 채 언제든 교체해 프라이버시를 지키세요."
|
||||
restoring: "신원을 복원하는 중…"
|
||||
clean_slate_blurb: "새 출발을 원하시나요? 언제든 완전히 새로운 키로 바꾸세요 — 새로운 나는 이전과 연결되지 않습니다. 같은 지갑, 새로운 얼굴입니다."
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Подключено через Tor"
|
||||
tor_ready: "Tor готов · реле…"
|
||||
connecting_tor: "Подключение к Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "Нет связи с узлом"
|
||||
node_synced: "Узел синхронизирован"
|
||||
syncing: "Синхронизация…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "Приватность"
|
||||
tor_routing: "Маршрутизация через Tor"
|
||||
messages_lookups: "Сообщения и поиск"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "Автоприём"
|
||||
pairing: "Валюта цены"
|
||||
accept_anyone: "Любой"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "Прямое соединение"
|
||||
grin_node: "Узел Grin"
|
||||
grin_node_blurb: "Синхронизация блоков и трансляция транзакции в сеть. Это публичные данные цепочки, одинаковые для всех, и они не связаны с вашей личностью."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "Привязка"
|
||||
intro: "К чему привязаны отображаемые баланс и суммы."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "Пароль кошелька шифрует и разблокирует каждую личность. Введите его, чтобы продолжить."
|
||||
working: "Обработка…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "Приватные деньги"
|
||||
private_money_body: "Goblin — кошелёк для grin: цифровая наличность без сумм и адресов в её цепочке."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "ключ создаётся…"
|
||||
connected_tor: "подключено через Tor"
|
||||
connecting_tor: "подключение через Tor…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "Платёжный ключ, не связанный с seed-фразой — меняйте его в любой момент, не трогая средства."
|
||||
restoring: "Восстановление ваших личностей…"
|
||||
clean_slate_blurb: "Хотите начать с чистого листа? Подставьте совершенно новый ключ в любой момент — новый вы не связан со старым. Тот же кошелёк, новое лицо."
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "Tor üzerinden bağlı"
|
||||
tor_ready: "Tor hazır · relaylar…"
|
||||
connecting_tor: "Tor'a bağlanılıyor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "Düğüme ulaşılamıyor"
|
||||
node_synced: "Düğüm eşitlendi"
|
||||
syncing: "Eşitleniyor…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "Gizlilik"
|
||||
tor_routing: "Tor yönlendirme"
|
||||
messages_lookups: "Mesajlar ve aramalar"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "Otomatik kabul"
|
||||
pairing: "Fiyat para birimi"
|
||||
accept_anyone: "Herkes"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "Doğrudan bağlantı"
|
||||
grin_node: "Grin düğümü"
|
||||
grin_node_blurb: "Blok eşitleme ve işlemini ağa yayma. Bu, herkes için aynı olan genel zincir verisidir ve kimliğinle ilişkilendirilmez."
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "Eşleştirme"
|
||||
intro: "Bakiyenin ve tutarların neye göre gösterildiği."
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "Cüzdan parolan her kimliği şifreler ve açar. Devam etmek için gir."
|
||||
working: "Çalışıyor…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "Özel para"
|
||||
private_money_body: "Goblin, grin için bir cüzdan — zincirinde tutar ya da adres bulunmayan dijital nakit."
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "anahtar oluşturuluyor…"
|
||||
connected_tor: "Tor üzerinden bağlı"
|
||||
connecting_tor: "Tor üzerinden bağlanılıyor…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "Seed'inin parçası olmayan bir ödeme anahtarı — paranı hiç ellemeden istediğin an döndür."
|
||||
restoring: "Kimliklerin geri yükleniyor…"
|
||||
clean_slate_blurb: "Temiz bir sayfa mı istiyorsun? İstediğin zaman yepyeni bir anahtar tak — yeni sen eskisine bağlı değil. Aynı cüzdan, yeni yüz."
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "已通过 Tor 连接"
|
||||
tor_ready: "Tor 就绪 · 连接中继…"
|
||||
connecting_tor: "正在连接 Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "无法连接节点"
|
||||
node_synced: "节点已同步"
|
||||
syncing: "同步中…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "隐私"
|
||||
tor_routing: "Tor 路由"
|
||||
messages_lookups: "消息和查询"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "自动接受"
|
||||
pairing: "价格货币"
|
||||
accept_anyone: "任何人"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "直接连接"
|
||||
grin_node: "Grin 节点"
|
||||
grin_node_blurb: "区块同步及向网络广播你的交易。这是公开的链上数据,对所有人都一样,且不与你的身份关联。"
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "配对"
|
||||
intro: "你的余额和金额以何种货币显示。"
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "钱包密码用于加密和解锁每个身份。输入以继续。"
|
||||
working: "处理中…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "私密货币"
|
||||
private_money_body: "Goblin 是一个 grin 钱包 — 链上无金额、无地址的数字现金。"
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "正在生成密钥…"
|
||||
connected_tor: "已通过 Tor 连接"
|
||||
connecting_tor: "正在通过 Tor 连接…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "一个不属于助记词的支付密钥——可随时轮换以保护隐私,且不影响你的资金。"
|
||||
restoring: "正在恢复你的身份…"
|
||||
clean_slate_blurb: "想要全新开始?随时换上一个全新密钥 — 新的你与旧的毫无关联。同一个钱包,焕然一新。"
|
||||
|
||||
@@ -363,6 +363,8 @@ goblin:
|
||||
connected_tor: "已透過 Tor 連線"
|
||||
tor_ready: "Tor 就緒 · 連線中繼…"
|
||||
connecting_tor: "正在連線 Tor…"
|
||||
connected_direct: "Connected (direct)"
|
||||
connecting_direct: "Connecting (direct)…"
|
||||
cant_reach_node: "無法連線節點"
|
||||
node_synced: "節點已同步"
|
||||
syncing: "同步中…"
|
||||
@@ -483,6 +485,8 @@ goblin:
|
||||
privacy: "隱私"
|
||||
tor_routing: "Tor 路由"
|
||||
messages_lookups: "訊息和查詢"
|
||||
tor_on: "On"
|
||||
tor_off: "Off"
|
||||
auto_accept: "自動接受"
|
||||
pairing: "價格貨幣"
|
||||
accept_anyone: "任何人"
|
||||
@@ -651,6 +655,14 @@ goblin:
|
||||
direct_connection: "直接連線"
|
||||
grin_node: "Grin 節點"
|
||||
grin_node_blurb: "區塊同步及向網路廣播你的交易。這是公開的鏈上資料,對所有人都一樣,且不與你的身份關聯。"
|
||||
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."
|
||||
vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN."
|
||||
pairing:
|
||||
title: "配對"
|
||||
intro: "你的餘額和金額以何種貨幣顯示。"
|
||||
@@ -796,6 +808,9 @@ goblin:
|
||||
pass_prompt: "錢包密碼用於加密和解鎖每個身份。輸入以繼續。"
|
||||
working: "處理中…"
|
||||
onboarding:
|
||||
privacy:
|
||||
kicker: "NETWORK PRIVACY"
|
||||
continue: "Continue"
|
||||
intro:
|
||||
private_money_head: "私密貨幣"
|
||||
private_money_body: "Goblin 是一個 grin 錢包 — 鏈上無金額、無地址的數字現金。"
|
||||
@@ -857,6 +872,8 @@ goblin:
|
||||
key_being_made: "正在生成金鑰…"
|
||||
connected_tor: "已透過 Tor 連線"
|
||||
connecting_tor: "正在透過 Tor 連線…"
|
||||
connected_direct: "connected (direct)"
|
||||
connecting_direct: "connecting (direct)…"
|
||||
fresh_key_blurb: "一個不屬於助記詞的支付金鑰——可隨時輪換以保護隱私,且不影響你的資金。"
|
||||
restoring: "正在恢復你的身份…"
|
||||
clean_slate_blurb: "想要全新開始?隨時換上一個全新金鑰 — 新的你與舊的毫無關聯。同一個錢包,煥然一新。"
|
||||
|
||||
@@ -81,6 +81,11 @@ impl Colors {
|
||||
theme::tokens().neg
|
||||
}
|
||||
|
||||
/// Blueviolet ("tor purple") accent for the Tor-on privacy state.
|
||||
pub fn tor_purple() -> Color32 {
|
||||
theme::tokens().tor_purple
|
||||
}
|
||||
|
||||
pub fn blue() -> Color32 {
|
||||
if dark_base() {
|
||||
Color32::from_rgb(0x7B, 0xA7, 0xFF)
|
||||
|
||||
@@ -68,6 +68,10 @@ pub struct ThemeTokens {
|
||||
pub accent_ink: Color32,
|
||||
pub pos: Color32,
|
||||
pub neg: Color32,
|
||||
/// Blueviolet ("tor purple") accent for the Tor-on privacy state (CSS
|
||||
/// `blueviolet` = #8A2BE2). Carries the "private traffic on" color on the
|
||||
/// network-privacy status dots and the active big Tor switch.
|
||||
pub tor_purple: Color32,
|
||||
pub chip: Color32,
|
||||
pub hover: Color32,
|
||||
/// Avatar background palette (initial ink picked by luminance).
|
||||
@@ -130,6 +134,7 @@ pub const LIGHT: ThemeTokens = ThemeTokens {
|
||||
accent_ink: Color32::from_rgb(0x0E, 0x0E, 0x0C),
|
||||
pos: Color32::from_rgb(0x0E, 0x7C, 0x3A),
|
||||
neg: Color32::from_rgb(0xB0, 0x48, 0x1E),
|
||||
tor_purple: Color32::from_rgb(0x8A, 0x2B, 0xE2),
|
||||
chip: Color32::from_rgb(0xF2, 0xF1, 0xEC),
|
||||
hover: Color32::from_rgb(0xE9, 0xE7, 0xE0),
|
||||
avatar_pairs: AVATAR_PAIRS,
|
||||
@@ -153,6 +158,7 @@ pub const DARK: ThemeTokens = ThemeTokens {
|
||||
accent_ink: Color32::from_rgb(0x0E, 0x0E, 0x0C),
|
||||
pos: Color32::from_rgb(0x5B, 0xD2, 0x7A),
|
||||
neg: Color32::from_rgb(0xFF, 0x8B, 0x5E),
|
||||
tor_purple: Color32::from_rgb(0x8A, 0x2B, 0xE2),
|
||||
chip: Color32::from_rgb(0x24, 0x24, 0x20),
|
||||
hover: Color32::from_rgb(0x2E, 0x2E, 0x29),
|
||||
avatar_pairs: AVATAR_PAIRS,
|
||||
@@ -178,6 +184,7 @@ pub const YELLOW: ThemeTokens = ThemeTokens {
|
||||
accent_ink: Color32::from_rgb(0xFF, 0xD6, 0x0A),
|
||||
pos: Color32::from_rgb(0x0E, 0x7C, 0x3A),
|
||||
neg: Color32::from_rgb(0x9E, 0x2E, 0x0E),
|
||||
tor_purple: Color32::from_rgb(0x8A, 0x2B, 0xE2),
|
||||
chip: Color32::from_rgba_premultiplied(2, 2, 2, 20),
|
||||
hover: Color32::from_rgb(0xEF, 0xC8, 0x00),
|
||||
avatar_pairs: AVATAR_PAIRS,
|
||||
|
||||
+201
-66
@@ -1596,15 +1596,18 @@ impl GoblinWalletView {
|
||||
.color(t.surface_text),
|
||||
);
|
||||
ui.label(
|
||||
// Relay-gated: "Connected over Tor" only once a
|
||||
// relay is live on the current tunnel generation.
|
||||
RichText::new(if crate::tor::transport_ready() {
|
||||
t!("goblin.home.connected_tor")
|
||||
} else if crate::tor::is_ready() {
|
||||
t!("goblin.home.tor_ready")
|
||||
} else {
|
||||
t!("goblin.home.connecting_tor")
|
||||
})
|
||||
// Transport-aware: Tor states are relay-gated,
|
||||
// while a clearnet wallet reads "Connected
|
||||
// (direct)" instead of forever "connecting over
|
||||
// Tor" (which never flips off-Tor).
|
||||
RichText::new(transport_status_label(
|
||||
wallet
|
||||
.nostr_service()
|
||||
.map(|s| s.transport_status())
|
||||
.unwrap_or(
|
||||
crate::nostr::TransportStatus::ConnectingTor,
|
||||
),
|
||||
))
|
||||
.font(FontId::new(11.0, fonts::regular()))
|
||||
.color(t.surface_text_mute),
|
||||
);
|
||||
@@ -3358,7 +3361,7 @@ impl GoblinWalletView {
|
||||
SettingsPage::Pairing => return self.pairing_settings_ui(ui),
|
||||
SettingsPage::Language => return self.language_settings_ui(ui),
|
||||
SettingsPage::Slatepack => return self.slatepack_ui(ui, wallet, cb),
|
||||
SettingsPage::Privacy => return self.privacy_ui(ui),
|
||||
SettingsPage::Privacy => return self.privacy_ui(ui, wallet),
|
||||
SettingsPage::Username => return self.username_ui(ui, wallet, cb),
|
||||
SettingsPage::AdvancedPrivacy => return self.advanced_privacy_ui(ui, wallet, cb),
|
||||
SettingsPage::Advanced => return self.advanced_ui(ui, wallet, cb),
|
||||
@@ -3445,17 +3448,16 @@ impl GoblinWalletView {
|
||||
}
|
||||
});
|
||||
// Transport status in place of the redundant second npub line.
|
||||
// "Connected over Tor" is RELAY-GATED (transport_ready): the
|
||||
// tunnel being warm is not enough — a relay must actually carry
|
||||
// our traffic on the current exit. Otherwise show the tunnel is
|
||||
// up but relays are still connecting/reconnecting.
|
||||
let transport = if crate::tor::transport_ready() {
|
||||
t!("goblin.home.connected_tor")
|
||||
} else if crate::tor::is_ready() {
|
||||
t!("goblin.home.tor_ready")
|
||||
} else {
|
||||
t!("goblin.home.connecting_tor")
|
||||
};
|
||||
// Transport-aware: Tor states are relay-gated (a warm tunnel is
|
||||
// not enough — a relay must carry our traffic), while a clearnet
|
||||
// wallet reads "Connected (direct)" rather than forever
|
||||
// "connecting over Tor".
|
||||
let transport = transport_status_label(
|
||||
wallet
|
||||
.nostr_service()
|
||||
.map(|s| s.transport_status())
|
||||
.unwrap_or(crate::nostr::TransportStatus::ConnectingTor),
|
||||
);
|
||||
ui.label(
|
||||
RichText::new(transport)
|
||||
.font(FontId::new(13.0, fonts::regular()))
|
||||
@@ -3472,7 +3474,10 @@ impl GoblinWalletView {
|
||||
.font(FontId::new(12.0, fonts::regular()))
|
||||
.color(t.surface_text_mute),
|
||||
);
|
||||
if !crate::tor::transport_ready() || !connected {
|
||||
// Keep repainting until a relay is live. Gating on the Tor-only
|
||||
// transport_ready would spin forever on a clearnet wallet, so
|
||||
// use the transport-neutral relay-connected flag.
|
||||
if !connected {
|
||||
ui.ctx()
|
||||
.request_repaint_after(std::time::Duration::from_millis(600));
|
||||
}
|
||||
@@ -3701,14 +3706,21 @@ impl GoblinWalletView {
|
||||
let mut open_privacy = false;
|
||||
let mut open_adv_privacy = false;
|
||||
settings_group(ui, &t!("goblin.settings.privacy"), |ui| {
|
||||
// Messages, names, price and avatars ride Tor; the grin
|
||||
// node connects directly. Normal dim value ink: the salmon
|
||||
// privacy color doubled as the destructive-action color on
|
||||
// this page, making a plain navigable row read as a warning.
|
||||
// Value shows the wallet's current Tor state (On/Off) in the
|
||||
// same dim font; the row opens the Network-privacy screen where
|
||||
// the big switch flips it.
|
||||
let tor_on = wallet
|
||||
.nostr_service()
|
||||
.map(|s| s.tor_routing())
|
||||
.unwrap_or(true);
|
||||
if settings_row_nav(
|
||||
ui,
|
||||
&t!("goblin.settings.tor_routing"),
|
||||
&t!("goblin.settings.messages_lookups"),
|
||||
&if tor_on {
|
||||
t!("goblin.settings.tor_on")
|
||||
} else {
|
||||
t!("goblin.settings.tor_off")
|
||||
},
|
||||
) {
|
||||
open_privacy = true;
|
||||
}
|
||||
@@ -4009,56 +4021,39 @@ impl GoblinWalletView {
|
||||
});
|
||||
}
|
||||
|
||||
/// Network-privacy breakdown: what rides Tor versus what connects
|
||||
/// directly. Honest by design — no claim that node traffic is mixed, and no
|
||||
/// toggle to route it (chain sync is heavy and not tied to your identity).
|
||||
fn privacy_ui(&mut self, ui: &mut egui::Ui) {
|
||||
let t = theme::tokens();
|
||||
/// Network-privacy screen: the interactive Tor toggle shared with onboarding.
|
||||
/// The Grin node is always direct; the private traffic (payments, name
|
||||
/// lookups, price) rides Tor when the switch is on, else connects directly.
|
||||
/// Flipping the switch writes the wallet Tor setting and restarts the service
|
||||
/// so the relay pool is rebuilt on the newly-selected transport.
|
||||
fn privacy_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
if self.sub_header(ui, &t!("goblin.privacy.title")) {
|
||||
self.settings_page = SettingsPage::Main;
|
||||
return;
|
||||
}
|
||||
let tor_on = wallet
|
||||
.nostr_service()
|
||||
.map(|s| s.tor_routing())
|
||||
.unwrap_or(true);
|
||||
let mut toggled = None;
|
||||
ScrollArea::vertical()
|
||||
.id_salt("goblin_privacy_scroll")
|
||||
.auto_shrink([false; 2])
|
||||
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
|
||||
.show(ui, |ui| {
|
||||
ui.label(
|
||||
RichText::new(t!("goblin.privacy.intro"))
|
||||
.font(FontId::new(14.0, fonts::regular()))
|
||||
.color(t.text_dim),
|
||||
);
|
||||
ui.add_space(16.0);
|
||||
let transport = [
|
||||
(
|
||||
t!("goblin.privacy.payments"),
|
||||
t!("goblin.privacy.payments_blurb"),
|
||||
),
|
||||
(
|
||||
t!("goblin.privacy.usernames"),
|
||||
t!("goblin.privacy.usernames_blurb"),
|
||||
),
|
||||
(
|
||||
t!("goblin.privacy.price_avatars"),
|
||||
t!("goblin.privacy.price_avatars_blurb"),
|
||||
),
|
||||
];
|
||||
settings_group(ui, &t!("goblin.privacy.over_tor"), |ui| {
|
||||
for (title, blurb) in &transport {
|
||||
privacy_line(ui, t.neg, title, blurb);
|
||||
}
|
||||
});
|
||||
ui.add_space(16.0);
|
||||
settings_group(ui, &t!("goblin.privacy.direct_connection"), |ui| {
|
||||
privacy_line(
|
||||
ui,
|
||||
t.surface_text_mute,
|
||||
&t!("goblin.privacy.grin_node"),
|
||||
&t!("goblin.privacy.grin_node_blurb"),
|
||||
);
|
||||
});
|
||||
toggled = network_privacy_panels(ui, tor_on);
|
||||
ui.add_space(16.0);
|
||||
});
|
||||
if let Some(new_val) = toggled {
|
||||
if let Some(s) = wallet.nostr_service() {
|
||||
// Persist the explicit choice (auto-saves nostr.toml) and mirror
|
||||
// the process-global route flag so free-function HTTP callers pick
|
||||
// the matching transport immediately, then rebuild the pool.
|
||||
s.config.write().set_tor_enabled(new_val);
|
||||
crate::tor::set_route_over_tor(new_val);
|
||||
s.restart(wallet.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Username page — the single home for everything name-related: claim one if
|
||||
@@ -8150,6 +8145,146 @@ fn privacy_line(ui: &mut egui::Ui, dot: Color32, title: &str, blurb: &str) {
|
||||
ui.add_space(10.0);
|
||||
}
|
||||
|
||||
/// Like [`settings_group`] but with a colored header kicker — used by the
|
||||
/// Network-privacy private-traffic panel, whose header is red ("TOR OFF") or
|
||||
/// green ("TOR ON") to signal the current state.
|
||||
fn settings_group_colored(
|
||||
ui: &mut egui::Ui,
|
||||
title: &str,
|
||||
color: Color32,
|
||||
add: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
ui.label(
|
||||
RichText::new(title.to_uppercase())
|
||||
.font(fonts::kicker())
|
||||
.color(color),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
w::card(ui, |ui| add(ui));
|
||||
}
|
||||
|
||||
/// The shared Network-privacy body, rendered by BOTH the onboarding privacy
|
||||
/// step and the Settings privacy screen so the two never drift. Draws the intro
|
||||
/// copy (what Goblin sends + how privacy works — deliberately no crypto version
|
||||
/// numbers), the always-direct Grin-node panel, the private-traffic panel whose
|
||||
/// header/dots reflect `tor_on`, the large Tor switch, and the VPN nudge.
|
||||
///
|
||||
/// Returns `Some(new_value)` on the frame the switch is toggled; the caller
|
||||
/// persists it (Settings writes the wallet Tor setting + restarts the service;
|
||||
/// onboarding stashes it and writes it before the service starts).
|
||||
fn network_privacy_panels(ui: &mut egui::Ui, tor_on: bool) -> Option<bool> {
|
||||
let t = theme::tokens();
|
||||
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.
|
||||
settings_group(ui, &t!("goblin.privacy.always_direct"), |ui| {
|
||||
ui.set_min_width(ui.available_width());
|
||||
privacy_line(
|
||||
ui,
|
||||
t.surface_text_mute,
|
||||
&t!("goblin.privacy.grin_node"),
|
||||
&t!("goblin.privacy.grin_node_blurb"),
|
||||
);
|
||||
});
|
||||
ui.add_space(16.0);
|
||||
|
||||
// Private traffic — header "TOR OFF" (red) / "TOR ON" (green); status dots
|
||||
// goblin-yellow when off (clearnet), blueviolet when on.
|
||||
let (header, header_color) = if tor_on {
|
||||
(t!("goblin.privacy.tor_on"), t.pos)
|
||||
} else {
|
||||
(t!("goblin.privacy.tor_off"), t.neg)
|
||||
};
|
||||
let dot = if tor_on { t.tor_purple } else { t.accent };
|
||||
let transport = [
|
||||
(
|
||||
t!("goblin.privacy.payments"),
|
||||
t!("goblin.privacy.payments_blurb"),
|
||||
),
|
||||
(
|
||||
t!("goblin.privacy.usernames"),
|
||||
t!("goblin.privacy.usernames_blurb"),
|
||||
),
|
||||
(
|
||||
t!("goblin.privacy.price_avatars"),
|
||||
t!("goblin.privacy.price_avatars_blurb"),
|
||||
),
|
||||
];
|
||||
settings_group_colored(ui, &header, header_color, |ui| {
|
||||
ui.set_min_width(ui.available_width());
|
||||
for (title, blurb) in &transport {
|
||||
privacy_line(ui, dot, title, blurb);
|
||||
}
|
||||
});
|
||||
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.
|
||||
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.add_space(12.0);
|
||||
ui.label(
|
||||
RichText::new(t!("goblin.privacy.vpn_note"))
|
||||
.font(FontId::new(12.5, fonts::regular()))
|
||||
.color(t.text_mute),
|
||||
);
|
||||
toggled
|
||||
}
|
||||
|
||||
/// Localized transport-status label for the identity status lines. Tor and
|
||||
/// clearnet are distinct states so a Tor-off wallet reads "Connected (direct)"
|
||||
/// instead of forever "connecting over Tor".
|
||||
fn transport_status_label(status: crate::nostr::TransportStatus) -> std::borrow::Cow<'static, str> {
|
||||
use crate::nostr::TransportStatus::*;
|
||||
match status {
|
||||
ConnectedTor => t!("goblin.home.connected_tor"),
|
||||
TorReady => t!("goblin.home.tor_ready"),
|
||||
ConnectingTor => t!("goblin.home.connecting_tor"),
|
||||
ConnectedDirect => t!("goblin.home.connected_direct"),
|
||||
ConnectingDirect => t!("goblin.home.connecting_direct"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a URL in the system browser.
|
||||
fn open_url(ui: &egui::Ui, url: &str) {
|
||||
ui.ctx().open_url(egui::OpenUrl::new_tab(url));
|
||||
|
||||
@@ -47,6 +47,10 @@ enum Step {
|
||||
WalletSetup,
|
||||
Words,
|
||||
ConfirmWords,
|
||||
/// Network-privacy choice (Tor on/off), shown right before the wallet is
|
||||
/// created so the new wallet writes an explicit Tor setting before its nostr
|
||||
/// service starts.
|
||||
Privacy,
|
||||
Identity,
|
||||
}
|
||||
|
||||
@@ -95,6 +99,10 @@ pub struct OnboardingContent {
|
||||
restore_result: std::sync::Arc<std::sync::Mutex<Option<Result<(), String>>>>,
|
||||
/// Sticky error from the identity restore, shown on the identity step.
|
||||
restore_error: Option<String>,
|
||||
/// Tor choice from the Network-privacy step, applied as the new wallet's
|
||||
/// explicit Tor setting before its nostr service starts. New users default
|
||||
/// OFF (owner spec): fast onboarding, one tap to turn on, VPN nudge shown.
|
||||
tor_choice: bool,
|
||||
}
|
||||
|
||||
/// Full-backup restore sub-state on the restore-from-seed words step.
|
||||
@@ -157,6 +165,8 @@ impl Default for OnboardingContent {
|
||||
restore_busy: false,
|
||||
restore_result: std::sync::Arc::new(std::sync::Mutex::new(None)),
|
||||
restore_error: None,
|
||||
// New users default to Tor OFF (owner spec).
|
||||
tor_choice: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +206,7 @@ impl OnboardingContent {
|
||||
Step::WalletSetup => self.wallet_setup_ui(ui, cb),
|
||||
Step::Words => self.words_ui(ui, wallets, cb),
|
||||
Step::ConfirmWords => self.confirm_ui(ui, wallets, cb),
|
||||
Step::Privacy => self.privacy_step_ui(ui, wallets),
|
||||
Step::Identity => done = self.identity_ui(ui, cb),
|
||||
}
|
||||
ui.add_space(View::get_bottom_inset() + 24.0);
|
||||
@@ -557,7 +568,9 @@ impl OnboardingContent {
|
||||
fn words_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallets: &mut WalletList,
|
||||
// Wallet creation now happens on the later Privacy step, so this path no
|
||||
// longer needs the list; kept for signature parity with the step router.
|
||||
_wallets: &mut WalletList,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let t = theme::tokens();
|
||||
@@ -673,7 +686,8 @@ impl OnboardingContent {
|
||||
if ready {
|
||||
if w::big_action(ui, &label, false).clicked() {
|
||||
if restore {
|
||||
self.create_wallet(wallets);
|
||||
// Network-privacy choice comes before the wallet is created.
|
||||
self.step = Step::Privacy;
|
||||
} else {
|
||||
self.step = Step::ConfirmWords;
|
||||
}
|
||||
@@ -796,7 +810,9 @@ impl OnboardingContent {
|
||||
fn confirm_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallets: &mut WalletList,
|
||||
// Wallet creation now happens on the later Privacy step, so this path no
|
||||
// longer needs the list; kept for signature parity with the step router.
|
||||
_wallets: &mut WalletList,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let t = theme::tokens();
|
||||
@@ -821,7 +837,8 @@ impl OnboardingContent {
|
||||
ui.add_space(14.0);
|
||||
if !self.mnemonic_setup.mnemonic.has_empty_or_invalid() {
|
||||
if w::big_action(ui, &t!("goblin.onboarding.confirm.create_wallet"), false).clicked() {
|
||||
self.create_wallet(wallets);
|
||||
// Network-privacy choice comes before the wallet is created.
|
||||
self.step = Step::Privacy;
|
||||
}
|
||||
} else {
|
||||
ui.label(
|
||||
@@ -844,6 +861,34 @@ impl OnboardingContent {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Network privacy (Tor on/off, before wallet creation) ─────────────
|
||||
|
||||
/// Network-privacy step: choose Tor on/off before the wallet is created,
|
||||
/// using the same shared panels as the Settings privacy screen. Continue
|
||||
/// creates the wallet, which writes the choice as its explicit Tor setting.
|
||||
fn privacy_step_ui(&mut self, ui: &mut egui::Ui, wallets: &mut WalletList) {
|
||||
// Back returns to whichever step led here (restore vs create).
|
||||
let back = if self.restore {
|
||||
Step::Words
|
||||
} else {
|
||||
Step::ConfirmWords
|
||||
};
|
||||
self.step_header(
|
||||
ui,
|
||||
&t!("goblin.onboarding.privacy.kicker"),
|
||||
&t!("goblin.privacy.title"),
|
||||
back,
|
||||
);
|
||||
if let Some(new_val) = super::network_privacy_panels(ui, self.tor_choice) {
|
||||
self.tor_choice = new_val;
|
||||
}
|
||||
ui.add_space(18.0);
|
||||
if w::big_action(ui, &t!("goblin.onboarding.privacy.continue"), false).clicked() {
|
||||
self.create_wallet(wallets);
|
||||
}
|
||||
self.error_ui(ui);
|
||||
}
|
||||
|
||||
/// Resolve the connection method, create the wallet, open it and move
|
||||
/// to the identity step.
|
||||
fn create_wallet(&mut self, wallets: &mut WalletList) {
|
||||
@@ -882,6 +927,17 @@ impl OnboardingContent {
|
||||
wallets.add(w.clone());
|
||||
match w.open(pass) {
|
||||
Ok(_) => {
|
||||
// Write this NEW wallet's EXPLICIT Tor choice (from the
|
||||
// privacy step) before its nostr service starts. init_nostr
|
||||
// built the service synchronously inside open(); start() runs
|
||||
// later on the sync thread, so setting it now writes nostr.toml
|
||||
// and picks the transport before the pool builds. (Cohort rule:
|
||||
// only new-wallet onboarding writes an explicit value;
|
||||
// upgraders keep None -> Tor ON.)
|
||||
if let Some(svc) = w.nostr_service() {
|
||||
svc.config.write().set_tor_enabled(self.tor_choice);
|
||||
crate::tor::set_route_over_tor(self.tor_choice);
|
||||
}
|
||||
// A full-backup restore: reinstate every identity from the
|
||||
// backup in a worker once nostr is up (the seed itself was
|
||||
// already restored through this creation path).
|
||||
@@ -995,12 +1051,28 @@ impl OnboardingContent {
|
||||
);
|
||||
}
|
||||
ui.label(
|
||||
// Relay-gated readiness: "connected over Tor" only once a
|
||||
// relay is actually live, not merely when the tunnel is warm.
|
||||
RichText::new(if crate::tor::transport_ready() {
|
||||
t!("goblin.onboarding.identity.connected_tor")
|
||||
} else {
|
||||
t!("goblin.onboarding.identity.connecting_tor")
|
||||
// Transport-aware readiness: Tor states are relay-gated (a
|
||||
// warm tunnel is not enough), while a clearnet wallet reads
|
||||
// "connected (direct)" instead of forever "connecting over
|
||||
// Tor".
|
||||
RichText::new({
|
||||
use crate::nostr::TransportStatus::*;
|
||||
let status = service
|
||||
.as_ref()
|
||||
.map(|s| s.transport_status())
|
||||
.unwrap_or(ConnectingTor);
|
||||
match status {
|
||||
ConnectedTor => t!("goblin.onboarding.identity.connected_tor"),
|
||||
TorReady | ConnectingTor => {
|
||||
t!("goblin.onboarding.identity.connecting_tor")
|
||||
}
|
||||
ConnectedDirect => {
|
||||
t!("goblin.onboarding.identity.connected_direct")
|
||||
}
|
||||
ConnectingDirect => {
|
||||
t!("goblin.onboarding.identity.connecting_direct")
|
||||
}
|
||||
}
|
||||
})
|
||||
.font(FontId::new(12.0, fonts::regular()))
|
||||
.color(t.surface_text_mute),
|
||||
|
||||
@@ -238,6 +238,32 @@ pub fn toggle(ui: &mut Ui, on: bool) -> Response {
|
||||
resp.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
/// A large on/off switch for the Network-privacy Tor toggle. Unlike [`toggle`]
|
||||
/// (brand yellow when on), this is dormant GRAY when OFF and blueviolet ("tor
|
||||
/// purple") when ON — never yellow — so the privacy state reads distinctly from
|
||||
/// 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 track = if on { t.tor_purple } else { t.surface2 };
|
||||
ui.painter()
|
||||
.rect_filled(rect, CornerRadius::same(18), track);
|
||||
let knob_r = 15.0;
|
||||
let knob_x = if on {
|
||||
rect.right() - knob_r - 3.0
|
||||
} else {
|
||||
rect.left() + knob_r + 3.0
|
||||
};
|
||||
let knob = if on {
|
||||
Color32::WHITE
|
||||
} else {
|
||||
t.surface_text_mute
|
||||
};
|
||||
ui.painter()
|
||||
.circle_filled(egui::pos2(knob_x, rect.center().y), knob_r, knob);
|
||||
resp.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
/// A segmented control (e.g. `["Scan", "My Code"]`). Highlights `selected`;
|
||||
/// returns `Some(i)` when a different segment is tapped.
|
||||
pub fn segmented(ui: &mut Ui, labels: &[&str], selected: usize) -> Option<usize> {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ pub mod ingest;
|
||||
pub use ingest::*;
|
||||
|
||||
mod client;
|
||||
pub use client::{HeldIdentityKeys, NostrProfile, NostrService, send_phase};
|
||||
pub use client::{HeldIdentityKeys, NostrProfile, NostrService, TransportStatus, send_phase};
|
||||
|
||||
pub mod avatar;
|
||||
pub mod nip05;
|
||||
|
||||
Reference in New Issue
Block a user