From e3b4545dc4a2d8625a8a0f9f58dff53fd3e7b824 Mon Sep 17 00:00:00 2001 From: 2ro <17595647+2ro@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:48:05 -0400 Subject: [PATCH] goblin: remove Settings Import identity (replace-current) flow Owner correction: importing an existing identity belongs at first-run (onboarding, restored in the previous commit), not in Settings. The Settings > Identity "Import identity" row was the destructive replace-the-current-identity action, redundant now that add-identity and the identity switcher cover bringing in keys non-destructively. Removes the settings row, the inline ImportState/import_nsec_ui flow, and the five now-unused settings keys (import_identity, import_identity_title, import_blurb, identity_replaced, now_using) across all eight locales. Wallet::import_nostr_identity stays: the onboarding import panel calls it. The shared settings.* strings the onboarding panel reuses (importing, import_failed, import_nsec_hint, backup_read_failed, choose_backup_file, backup_password_hint, import_btn, cancel, close) also stay. Drift + call-site tests green. --- locales/de.yml | 5 - locales/en.yml | 5 - locales/es.yml | 5 - locales/fr.yml | 5 - locales/ko.yml | 5 - locales/ru.yml | 5 - locales/tr.yml | 5 - locales/zh-CN.yml | 5 - src/gui/views/goblin/mod.rs | 261 ------------------------------------ 9 files changed, 301 deletions(-) diff --git a/locales/de.yml b/locales/de.yml index 838c62a7..16a28aa3 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Verbinde mit Relays…" identity: "Identität" copy_npub: "npub kopieren (öffentlich)" - import_identity: "Identität importieren (.backup / nsec)" backup_note: "Gerät wechseln? Sichere BEIDES: deine Seed-Phrase (Guthaben) und deine Identitäts-.backup-Datei (Name + Schlüssel)." wallet: "Wallet" display_unit: "Anzeigeeinheit" @@ -536,14 +535,10 @@ goblin: wallet_password: "Wallet-Passwort" done: "Fertig" close: "Schließen" - import_identity_title: "Identität importieren" - import_blurb: "Ersetzt die nostr-Identität dieser Wallet — wähle eine GOBLIN-.backup-Datei oder füge einen nsec ein. Eine Sicherung stellt auch Benutzername und Verlauf wieder her. Sichere zuerst den aktuellen Schlüssel, falls du ihn noch brauchst." import_nsec_hint: "nsec1… oder eingefügte Sicherung" backup_password_hint: "Backup-Passwort (nur wenn anderswo exportiert)" import_btn: "Importieren" importing: "Importiere…" - identity_replaced: "Identität ersetzt" - now_using: "Jetzt aktiv: %{npub}" import_failed: "Import fehlgeschlagen" name_authority: "Namensautorität" name_authority_title: "Namensautorität ändern" diff --git a/locales/en.yml b/locales/en.yml index b6f9d5d5..070c5cfe 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Connecting to relays…" identity: "Identity" copy_npub: "Copy npub (public)" - import_identity: "Import identity (.backup / nsec)" backup_note: "Moving devices? Back up BOTH: your seed phrase (funds) and your identity .backup file (name + key)." wallet: "Wallet" display_unit: "Display unit" @@ -536,14 +535,10 @@ goblin: wallet_password: "Wallet password" done: "Done" close: "Close" - import_identity_title: "Import identity" - import_blurb: "Replaces this wallet's nostr identity — choose a GOBLIN .backup file, or paste a bare nsec. A backup also restores your username and history. Back up the current key first if you still need it." import_nsec_hint: "nsec1… or pasted backup" backup_password_hint: "Backup password (only if exported elsewhere)" import_btn: "Import" importing: "Importing…" - identity_replaced: "Identity replaced" - now_using: "Now using: %{npub}" import_failed: "Import failed" name_authority: "Name authority" name_authority_title: "Change name authority" diff --git a/locales/es.yml b/locales/es.yml index 276b58f7..7b943986 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Conectando a relés…" identity: "Identidad" copy_npub: "Copiar npub (público)" - import_identity: "Importar identidad (.backup / nsec)" backup_note: "¿Cambiando de dispositivo? Respalda AMBOS: tu frase de recuperación (fondos) y tu archivo .backup de identidad (nombre + clave)." wallet: "Billetera" display_unit: "Unidad de visualización" @@ -536,14 +535,10 @@ goblin: wallet_password: "Contraseña de la billetera" done: "Listo" close: "Cerrar" - import_identity_title: "Importar identidad" - import_blurb: "Reemplaza la identidad nostr de esta billetera — elige un archivo .backup de GOBLIN, o pega un nsec directamente. Un respaldo también restaura tu nombre de usuario e historial. Respalda primero la clave actual si aún la necesitas." import_nsec_hint: "nsec1… o respaldo pegado" backup_password_hint: "Contraseña del respaldo (solo si se exportó en otro lugar)" import_btn: "Importar" importing: "Importando…" - identity_replaced: "Identidad reemplazada" - now_using: "Ahora usando: %{npub}" import_failed: "Error al importar" name_authority: "Autoridad de nombres" name_authority_title: "Cambiar autoridad de nombres" diff --git a/locales/fr.yml b/locales/fr.yml index 85e8e9d8..fb396baf 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Connexion aux relais…" identity: "Identité" copy_npub: "Copier le npub (public)" - import_identity: "Importer l'identité (.backup / nsec)" backup_note: "Changement d'appareil ? Sauvegardez les DEUX : votre phrase seed (fonds) et votre fichier .backup d'identité (nom + clé)." wallet: "Portefeuille" display_unit: "Unité d'affichage" @@ -536,14 +535,10 @@ goblin: wallet_password: "Mot de passe du portefeuille" done: "Terminé" close: "Fermer" - import_identity_title: "Importer une identité" - import_blurb: "Remplace l'identité nostr de ce portefeuille — choisissez un fichier .backup GOBLIN, ou collez un nsec. Une sauvegarde restaure aussi votre nom d'utilisateur et votre historique. Sauvegardez d'abord la clé actuelle si besoin." import_nsec_hint: "nsec1… ou sauvegarde collée" backup_password_hint: "Mot de passe de sauvegarde (uniquement si exportée ailleurs)" import_btn: "Importer" importing: "Importation…" - identity_replaced: "Identité remplacée" - now_using: "Utilise maintenant : %{npub}" import_failed: "Échec de l'importation" name_authority: "Autorité de noms" name_authority_title: "Changer l'autorité de noms" diff --git a/locales/ko.yml b/locales/ko.yml index 05936bc4..c285dbcd 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "릴레이에 연결하는 중…" identity: "신원" copy_npub: "npub 복사(공개)" - import_identity: "신원 가져오기(.backup / nsec)" backup_note: "기기를 바꾸시나요? 두 가지 모두 백업하세요: 복구 문구(자금)와 신원 .backup 파일(이름 + 키)." wallet: "지갑" display_unit: "표시 단위" @@ -536,14 +535,10 @@ goblin: wallet_password: "지갑 비밀번호" done: "완료" close: "닫기" - import_identity_title: "신원 가져오기" - import_blurb: "이 지갑의 nostr 신원을 대체합니다 — GOBLIN .backup 파일을 선택하거나 nsec을 붙여넣으세요. 백업은 사용자 이름과 기록도 함께 복원합니다. 기존 키가 아직 필요하다면 먼저 백업하세요." import_nsec_hint: "nsec1… 또는 붙여넣은 백업" backup_password_hint: "백업 비밀번호(다른 곳에서 내보낸 경우에만)" import_btn: "가져오기" importing: "가져오는 중…" - identity_replaced: "신원이 교체되었습니다" - now_using: "현재 사용 중: %{npub}" import_failed: "가져오기 실패" name_authority: "이름 기관" name_authority_title: "이름 기관 변경" diff --git a/locales/ru.yml b/locales/ru.yml index 9e85683f..754d44ee 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Подключение к реле…" identity: "Личность" copy_npub: "Копировать npub (публичный)" - import_identity: "Импорт личности (.backup / nsec)" backup_note: "Меняете устройство? Сохраните ОБА: seed-фразу (средства) и файл .backup личности (имя + ключ)." wallet: "Кошелёк" display_unit: "Единица отображения" @@ -536,14 +535,10 @@ goblin: wallet_password: "Пароль кошелька" done: "Готово" close: "Закрыть" - import_identity_title: "Импорт личности" - import_blurb: "Заменяет nostr-личность этого кошелька — выберите файл GOBLIN .backup или вставьте nsec. Резервная копия также восстанавливает имя и историю. Сначала сохраните текущий ключ, если он ещё нужен." import_nsec_hint: "nsec1… или вставленная копия" backup_password_hint: "Пароль резерва (только если экспортирован в другом месте)" import_btn: "Импорт" importing: "Импорт…" - identity_replaced: "Личность заменена" - now_using: "Сейчас используется: %{npub}" import_failed: "Импорт не удался" name_authority: "Сервер имён" name_authority_title: "Сменить сервер имён" diff --git a/locales/tr.yml b/locales/tr.yml index 3e5bc3ee..a85e449d 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "Relaylara bağlanılıyor…" identity: "Kimlik" copy_npub: "npub kopyala (genel)" - import_identity: "Kimlik içe aktar (.backup / nsec)" backup_note: "Cihaz mı değiştiriyorsun? İKİSİNİ de yedekle: seed ifaden (bakiye) ve kimlik .backup dosyan (ad + anahtar)." wallet: "Cüzdan" display_unit: "Görüntüleme birimi" @@ -536,14 +535,10 @@ goblin: wallet_password: "Cüzdan parolası" done: "Bitti" close: "Kapat" - import_identity_title: "Kimlik içe aktar" - import_blurb: "Bu cüzdanın nostr kimliğini değiştirir — bir GOBLIN .backup dosyası seç ya da nsec yapıştır. Yedek ayrıca kullanıcı adını ve geçmişini geri yükler. Hâlâ gerekiyorsa önce mevcut anahtarı yedekle." import_nsec_hint: "nsec1… veya yapıştırılan yedek" backup_password_hint: "Yedek parolası (yalnızca başka yerde dışa aktarıldıysa)" import_btn: "İçe aktar" importing: "İçe aktarılıyor…" - identity_replaced: "Kimlik değiştirildi" - now_using: "Şu an kullanılan: %{npub}" import_failed: "İçe aktarma başarısız" name_authority: "İsim otoritesi" name_authority_title: "İsim otoritesini değiştir" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c7170360..f66abb60 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -465,7 +465,6 @@ goblin: connecting_relays: "正在连接中继…" identity: "身份" copy_npub: "复制 npub(公开)" - import_identity: "导入身份(.backup / nsec)" backup_note: "更换设备?两者都要备份:你的助记词(资金)和身份 .backup 文件(名称 + 密钥)。" wallet: "钱包" display_unit: "显示单位" @@ -536,14 +535,10 @@ goblin: wallet_password: "钱包密码" done: "完成" close: "关闭" - import_identity_title: "导入身份" - import_blurb: "替换此钱包的 nostr 身份——选择一个 GOBLIN .backup 文件,或粘贴 nsec。备份也会恢复你的用户名和历史。如果仍需要当前密钥,请先备份。" import_nsec_hint: "nsec1… 或粘贴的备份" backup_password_hint: "备份密码(仅当在他处导出时需要)" import_btn: "导入" importing: "正在导入…" - identity_replaced: "身份已替换" - now_using: "当前使用:%{npub}" import_failed: "导入失败" name_authority: "名称授权方" name_authority_title: "更改名称授权方" diff --git a/src/gui/views/goblin/mod.rs b/src/gui/views/goblin/mod.rs index 0367bf6c..14fd7500 100644 --- a/src/gui/views/goblin/mod.rs +++ b/src/gui/views/goblin/mod.rs @@ -75,8 +75,6 @@ pub struct GoblinWalletView { wallet_id: Option, /// Inline username-claim state for the Me tab. claim: Option, - /// Inline nsec-import state for the Me tab. - import_nsec: Option, /// Inline "back up identity to a file" flow state. backup: Option, /// Identity switcher (one wallet, many nostr identities) page state. @@ -298,7 +296,6 @@ impl Default for GoblinWalletView { request_error: None, wallet_id: None, claim: None, - import_nsec: None, backup: None, identity_switch: IdentitySwitchState::default(), name_authority: None, @@ -337,35 +334,6 @@ impl Default for GoblinWalletView { } } -/// Inline nsec-import flow state (restore path for the random-key model). -struct ImportState { - /// 1 = form, 3 = working, 4 = done, 5 = error. - stage: u8, - nsec: String, - password: String, - backup_password: String, - new_npub: String, - error: String, - /// A native file pick is in flight (Android returns the path asynchronously). - picking: bool, - result: std::sync::Arc>>>, -} - -impl Default for ImportState { - fn default() -> Self { - Self { - stage: 1, - nsec: String::new(), - password: String::new(), - backup_password: String::new(), - new_npub: String::new(), - error: String::new(), - picking: false, - result: std::sync::Arc::new(std::sync::Mutex::new(None)), - } - } -} - /// Id of the wallet-password modal used to unlock a switch or encrypt an add, /// mirroring the wallet-open password modal. const IDENTITY_PASS_MODAL: &str = "goblin_identity_pass_modal"; @@ -788,7 +756,6 @@ impl GoblinWalletView { self.receipt = None; self.profile = None; self.claim = None; - self.import_nsec = None; self.approve_review = None; self.approve_hold = w::HoldToSend::default(); self.approve_fee_for = None; @@ -3637,14 +3604,6 @@ impl GoblinWalletView { { self.backup = Some(BackupState::default()); } - if settings_row_btn( - ui, - &t!("goblin.settings.import_identity"), - crate::gui::icons::KEY, - ) && self.import_nsec.is_none() - { - self.import_nsec = Some(ImportState::default()); - } // Nostr relays the wallet publishes/reads gift wraps on. // Sits with the identity rows because relays are a nostr // concern; opens the relay editor (handled below). @@ -3717,10 +3676,6 @@ impl GoblinWalletView { ui.add_space(8.0); self.backup_ui(ui, wallet, cb); } - if self.import_nsec.is_some() { - ui.add_space(8.0); - self.import_nsec_ui(ui, wallet, cb); - } ui.add_space(16.0); let mut open_node = false; @@ -5427,222 +5382,6 @@ impl GoblinWalletView { } } - fn import_nsec_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { - let t = theme::tokens(); - let import = self.import_nsec.as_mut().unwrap(); - if import.stage == 3 { - if let Some(res) = import.result.lock().unwrap().take() { - match res { - Ok(npub) => { - import.new_npub = npub; - import.stage = 4; - } - Err(e) => { - import.error = e; - import.stage = 5; - } - } - } - } - let mut close = false; - w::card(ui, |ui| { - ui.set_min_width(ui.available_width()); - match import.stage { - 1 => { - ui.label( - RichText::new(t!("goblin.settings.import_identity_title")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.surface_text), - ); - ui.add_space(6.0); - ui.label( - RichText::new(t!("goblin.settings.import_blurb")) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - ui.add_space(10.0); - // Native ".backup file" picker. Desktop returns the path now; - // Android returns it asynchronously (poll picked_file()). - if import.picking { - if let Some(path) = cb.picked_file() { - import.picking = false; - if !path.is_empty() { - match std::fs::read_to_string(&path) { - Ok(contents) => import.nsec = contents.trim().to_string(), - Err(_) => { - import.error = - t!("goblin.settings.backup_read_failed").to_string(); - } - } - } - } else { - ui.ctx().request_repaint(); - } - } - if w::big_action_on_card(ui, &t!("goblin.settings.choose_backup_file")) - .clicked() - { - import.error.clear(); - match cb.pick_file() { - Some(path) if !path.is_empty() => { - match std::fs::read_to_string(&path) { - Ok(contents) => import.nsec = contents.trim().to_string(), - Err(_) => { - import.error = - t!("goblin.settings.backup_read_failed").to_string(); - } - } - } - // Empty string = Android async pick in flight. - Some(_) => import.picking = true, - None => {} - } - } - if !import.error.is_empty() && import.stage == 1 { - ui.add_space(6.0); - ui.label( - RichText::new(&import.error) - .font(FontId::new(12.5, fonts::regular())) - .color(t.neg), - ); - } - ui.add_space(8.0); - w::field_well(ui, |ui| { - TextEdit::new(egui::Id::from("import_nsec")) - .focus(false) - .hint_text(t!("goblin.settings.import_nsec_hint")) - .password() - .text_color(t.surface_text) - .body() - .ui(ui, &mut import.nsec, cb); - }); - ui.add_space(8.0); - w::field_well(ui, |ui| { - TextEdit::new(egui::Id::from("import_pass")) - .focus(false) - .hint_text(t!("goblin.settings.wallet_password")) - .password() - .text_color(t.surface_text) - .body() - .ui(ui, &mut import.password, cb); - }); - ui.add_space(8.0); - w::field_well(ui, |ui| { - TextEdit::new(egui::Id::from("import_backup_pass")) - .focus(false) - .hint_text(t!("goblin.settings.backup_password_hint")) - .password() - .text_color(t.surface_text) - .body() - .ui(ui, &mut import.backup_password, cb); - }); - ui.add_space(10.0); - let pasted = import.nsec.trim(); - let armed = (pasted.starts_with("nsec1") || pasted.starts_with('{')) - && !import.password.is_empty(); - ui.horizontal(|ui| { - let half = (ui.available_width() - 10.0) / 2.0; - ui.scope_builder( - egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( - ui.cursor().min, - Vec2::new(half, 44.0), - )), - |ui| { - if w::big_action_on_card(ui, &t!("goblin.settings.cancel")) - .clicked() - { - close = true; - } - }, - ); - ui.add_space(10.0); - ui.scope_builder( - egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( - ui.cursor().min, - Vec2::new(half, 44.0), - )), - |ui| { - ui.add_enabled_ui(armed, |ui| { - if w::big_action(ui, &t!("goblin.settings.import_btn"), false) - .clicked() - { - import.stage = 3; - let slot = import.result.clone(); - let nsec = std::mem::take(&mut import.nsec); - let password = std::mem::take(&mut import.password); - let bpw = std::mem::take(&mut import.backup_password); - let bpw = if bpw.is_empty() { None } else { Some(bpw) }; - let wallet = wallet.clone(); - std::thread::spawn(move || { - let res = - wallet.import_nostr_identity(nsec, password, bpw); - *slot.lock().unwrap() = Some(res); - }); - } - }); - }, - ); - }); - } - 3 => { - ui.horizontal(|ui| { - View::small_loading_spinner(ui); - ui.add_space(8.0); - ui.label( - RichText::new(t!("goblin.settings.importing")) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - }); - ui.ctx().request_repaint(); - } - 4 => { - ui.label( - RichText::new(t!("goblin.settings.identity_replaced")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.pos), - ); - ui.add_space(4.0); - let npub = &import.new_npub; - let short = if npub.len() > 18 { - format!("{}…{}", &npub[..12], &npub[npub.len() - 6..]) - } else { - npub.clone() - }; - ui.label( - RichText::new(t!("goblin.settings.now_using", npub => short)) - .font(FontId::new(13.0, fonts::mono())) - .color(t.surface_text_dim), - ); - ui.add_space(10.0); - if w::big_action(ui, &t!("goblin.settings.done"), false).clicked() { - close = true; - } - } - _ => { - ui.label( - RichText::new(t!("goblin.settings.import_failed")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.neg), - ); - ui.add_space(4.0); - ui.label( - RichText::new(&import.error) - .font(FontId::new(13.0, fonts::regular())) - .color(t.surface_text_dim), - ); - ui.add_space(10.0); - if w::big_action_on_card(ui, &t!("goblin.settings.close")).clicked() { - close = true; - } - } - } - }); - if close { - self.import_nsec = None; - } - } - /// The identity switcher page: one wallet, one grin balance, many nostr /// identities. Lists the held identities (tap to make one active), and adds a /// new one (generate a fresh nsec or import an existing one). Switching runs a