From 1566cd5cf4d3b3d69d06e9b518bed3e037984acf Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 7 Aug 2025 17:35:29 -0400 Subject: [PATCH] integrate onboarding Signed-off-by: kernelkind --- crates/notedeck_columns/src/accounts/mod.rs | 167 ++++++++++++------ crates/notedeck_columns/src/accounts/route.rs | 7 + crates/notedeck_columns/src/nav.rs | 64 ++++--- crates/notedeck_columns/src/profile.rs | 28 ++- crates/notedeck_columns/src/route.rs | 5 + .../src/ui/account_login_view.rs | 6 +- 6 files changed, 192 insertions(+), 85 deletions(-) diff --git a/crates/notedeck_columns/src/accounts/mod.rs b/crates/notedeck_columns/src/accounts/mod.rs index 6422fd7..0e482ea 100644 --- a/crates/notedeck_columns/src/accounts/mod.rs +++ b/crates/notedeck_columns/src/accounts/mod.rs @@ -1,15 +1,19 @@ use enostr::{FullKeypair, Pubkey}; use nostrdb::{Ndb, Transaction}; -use notedeck::{Accounts, AppContext, Localization, SingleUnkIdAction, UnknownIds}; +use notedeck::{Accounts, AppContext, JobsCache, Localization, SingleUnkIdAction, UnknownIds}; +use notedeck_ui::nip51_set::Nip51SetUiCache; +pub use crate::accounts::route::AccountsResponse; use crate::app::get_active_columns_mut; use crate::decks::DecksCache; +use crate::onboarding::Onboarding; use crate::profile::send_new_contact_list; +use crate::subscriptions::Subscriptions; +use crate::ui::onboarding::{FollowPackOnboardingView, FollowPacksResponse, OnboardingResponse}; use crate::{ login_manager::AcquireKeyState, route::Route, - timeline::TimelineCache, ui::{ account_login_view::{AccountLoginResponse, AccountLoginView}, accounts::{AccountsView, AccountsViewResponse}, @@ -37,6 +41,7 @@ pub struct SwitchAccountAction { /// The account to switch to pub switch_to: Pubkey, + pub switching_to_new: bool, } impl SwitchAccountAction { @@ -44,8 +49,14 @@ impl SwitchAccountAction { SwitchAccountAction { source_column, switch_to, + switching_to_new: false, } } + + pub fn switching_to_new(mut self) -> Self { + self.switching_to_new = true; + self + } } #[derive(Debug)] @@ -65,13 +76,13 @@ pub struct AddAccountAction { pub fn render_accounts_route( ui: &mut egui::Ui, app_ctx: &mut AppContext, - col: usize, - decks: &mut DecksCache, - timeline_cache: &mut TimelineCache, + jobs: &mut JobsCache, login_state: &mut AcquireKeyState, + onboarding: &Onboarding, + follow_packs_ui: &mut Nip51SetUiCache, route: AccountsRoute, -) -> AddAccountAction { - let resp = match route { +) -> Option { + match route { AccountsRoute::Accounts => AccountsView::new( app_ctx.ndb, app_ctx.accounts, @@ -80,47 +91,33 @@ pub fn render_accounts_route( ) .ui(ui) .inner - .map(AccountsRouteResponse::Accounts), - + .map(AccountsRouteResponse::Accounts) + .map(AccountsResponse::Account), AccountsRoute::AddAccount => { AccountLoginView::new(login_state, app_ctx.clipboard, app_ctx.i18n) .ui(ui) .inner .map(AccountsRouteResponse::AddAccount) + .map(AccountsResponse::Account) } - }; - - if let Some(resp) = resp { - match resp { - AccountsRouteResponse::Accounts(response) => { - let action = process_accounts_view_response( - app_ctx.i18n, - app_ctx.accounts, - decks, - col, - response, - ); - AddAccountAction { - accounts_action: action, - unk_id_action: SingleUnkIdAction::no_action(), - } + AccountsRoute::Onboarding => FollowPackOnboardingView::new( + onboarding, + follow_packs_ui, + app_ctx.ndb, + app_ctx.img_cache, + app_ctx.i18n, + app_ctx.job_pool, + jobs, + ) + .ui(ui) + .map(|r| match r { + OnboardingResponse::FollowPacks(follow_packs_response) => { + AccountsResponse::Account(AccountsRouteResponse::AddAccount( + AccountLoginResponse::Onboarding(follow_packs_response), + )) } - AccountsRouteResponse::AddAccount(response) => { - let action = - process_login_view_response(app_ctx, timeline_cache, decks, col, response); - *login_state = Default::default(); - let router = get_active_columns_mut(app_ctx.i18n, app_ctx.accounts, decks) - .column_mut(col) - .router_mut(); - router.go_back(); - action - } - } - } else { - AddAccountAction { - accounts_action: None, - unk_id_action: SingleUnkIdAction::no_action(), - } + OnboardingResponse::ViewProfile(pubkey) => AccountsResponse::ViewProfile(pubkey), + }), } } @@ -155,31 +152,53 @@ pub fn process_accounts_view_response( pub fn process_login_view_response( app_ctx: &mut AppContext, - timeline_cache: &mut TimelineCache, decks: &mut DecksCache, + subs: &mut Subscriptions, + onboarding: &mut Onboarding, col: usize, response: AccountLoginResponse, ) -> AddAccountAction { - let (r, pubkey) = match response { - AccountLoginResponse::CreateNew => { - let kp = FullKeypair::generate(); - let pubkey = kp.pubkey; - send_new_contact_list(kp.to_filled(), app_ctx.ndb, app_ctx.pool); - (app_ctx.accounts.add_account(kp.to_keypair()), pubkey) - } - AccountLoginResponse::LoginWith(keypair) => { - let pubkey = keypair.pubkey; - (app_ctx.accounts.add_account(keypair), pubkey) - } - }; + let cur_router = get_active_columns_mut(app_ctx.i18n, app_ctx.accounts, decks) + .column_mut(col) + .router_mut(); - decks.add_deck_default(app_ctx, timeline_cache, pubkey); + let r = match response { + AccountLoginResponse::LoginWith(keypair) => { + cur_router.go_back(); + app_ctx.accounts.add_account(keypair) + } + AccountLoginResponse::CreatingNew => { + cur_router.route_to(Route::Accounts(AccountsRoute::Onboarding)); + + onboarding.process(app_ctx.pool, app_ctx.ndb, subs, app_ctx.unknown_ids); + + None + } + AccountLoginResponse::Onboarding(onboarding_response) => match onboarding_response { + FollowPacksResponse::NoFollowPacks => { + onboarding.process(app_ctx.pool, app_ctx.ndb, subs, app_ctx.unknown_ids); + None + } + FollowPacksResponse::UserSelectedPacks(nip51_sets_ui_state) => { + let pks_to_follow = nip51_sets_ui_state.get_all_selected(); + + let kp = FullKeypair::generate(); + + send_new_contact_list(kp.to_filled(), app_ctx.ndb, app_ctx.pool, pks_to_follow); + cur_router.go_back(); + onboarding.end_onboarding(app_ctx.pool, app_ctx.ndb); + + app_ctx.accounts.add_account(kp.to_keypair()) + } + }, + }; if let Some(action) = r { AddAccountAction { accounts_action: Some(AccountsAction::Switch(SwitchAccountAction { source_column: col, switch_to: action.switch_to, + switching_to_new: true, })), unk_id_action: action.unk_id_action, } @@ -190,3 +209,41 @@ pub fn process_login_view_response( } } } + +impl AccountsRouteResponse { + pub fn process( + self, + app_ctx: &mut AppContext, + app: &mut crate::Damus, + col: usize, + ) -> AddAccountAction { + match self { + AccountsRouteResponse::Accounts(response) => { + let action = process_accounts_view_response( + app_ctx.i18n, + app_ctx.accounts, + &mut app.decks_cache, + col, + response, + ); + AddAccountAction { + accounts_action: action, + unk_id_action: notedeck::SingleUnkIdAction::no_action(), + } + } + AccountsRouteResponse::AddAccount(response) => { + let action = process_login_view_response( + app_ctx, + &mut app.decks_cache, + &mut app.subscriptions, + &mut app.onboarding, + col, + response, + ); + app.view_state.login = Default::default(); + + action + } + } + } +} diff --git a/crates/notedeck_columns/src/accounts/route.rs b/crates/notedeck_columns/src/accounts/route.rs index befcfc8..4d12380 100644 --- a/crates/notedeck_columns/src/accounts/route.rs +++ b/crates/notedeck_columns/src/accounts/route.rs @@ -7,10 +7,16 @@ pub enum AccountsRouteResponse { AddAccount(AccountLoginResponse), } +pub enum AccountsResponse { + ViewProfile(enostr::Pubkey), + Account(AccountsRouteResponse), +} + #[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] pub enum AccountsRoute { Accounts, AddAccount, + Onboarding, } impl AccountsRoute { @@ -19,6 +25,7 @@ impl AccountsRoute { match self { Self::Accounts => &["accounts", "show"], Self::AddAccount => &["accounts", "new"], + Self::Onboarding => &["accounts", "onboarding"], } } } diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs index 741d14c..d3eb64c 100644 --- a/crates/notedeck_columns/src/nav.rs +++ b/crates/notedeck_columns/src/nav.rs @@ -1,5 +1,5 @@ use crate::{ - accounts::{render_accounts_route, AccountsAction}, + accounts::{render_accounts_route, AccountsAction, AccountsResponse}, app::{get_active_columns_mut, get_decks_mut}, column::ColumnsAction, deck_state::DeckState, @@ -19,6 +19,7 @@ use crate::{ configure_deck::ConfigureDeckView, edit_deck::{EditDeckResponse, EditDeckView}, note::{custom_zap::CustomZapView, NewPostAction, PostAction, PostType, QuoteRepostView}, + onboarding::FollowPackOnboardingView, profile::EditProfileView, search::{FocusState, SearchView}, settings::SettingsAction, @@ -85,14 +86,21 @@ impl SwitchingAction { match &self { SwitchingAction::Accounts(account_action) => match account_action { AccountsAction::Switch(switch_action) => { - let txn = Transaction::new(ctx.ndb).expect("txn"); - ctx.accounts.select_account( - &switch_action.switch_to, - ctx.ndb, - &txn, - ctx.pool, - ui_ctx, - ); + { + let txn = Transaction::new(ctx.ndb).expect("txn"); + ctx.accounts.select_account( + &switch_action.switch_to, + ctx.ndb, + &txn, + ctx.pool, + ui_ctx, + ); + } + + if switch_action.switching_to_new { + decks_cache.add_deck_default(ctx, timeline_cache, switch_action.switch_to); + } + // pop nav after switch get_active_columns_mut(ctx.i18n, ctx.accounts, decks_cache) .column_mut(switch_action.source_column) @@ -564,21 +572,33 @@ fn render_nav_body( &mut note_context, &mut app.jobs, ), - Route::Accounts(amr) => { - let mut action = render_accounts_route( + Route::Accounts(amr) => 's: { + let Some(action) = render_accounts_route( ui, ctx, - col, - &mut app.decks_cache, - &mut app.timeline_cache, + &mut app.jobs, &mut app.view_state.login, + &app.onboarding, + &mut app.view_state.follow_packs, *amr, - ); - let txn = Transaction::new(ctx.ndb).expect("txn"); - action.process_action(ctx.unknown_ids, ctx.ndb, &txn); - action - .accounts_action - .map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f))) + ) else { + break 's None; + }; + + match action { + AccountsResponse::ViewProfile(pubkey) => { + Some(RenderNavAction::NoteAction(NoteAction::Profile(pubkey))) + } + AccountsResponse::Account(accounts_route_response) => { + let mut action = accounts_route_response.process(ctx, app, col); + + let txn = Transaction::new(ctx.ndb).expect("txn"); + action.process_action(ctx.unknown_ids, ctx.ndb, &txn); + action + .accounts_action + .map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f))) + } + } } Route::Relays => RelayView::new(ctx.pool, &mut app.view_state.id_string_map, ctx.i18n) .ui(ui) @@ -1061,6 +1081,9 @@ fn get_scroll_id( Route::Accounts(accounts_route) => match accounts_route { crate::accounts::AccountsRoute::Accounts => Some(AccountsView::scroll_id()), crate::accounts::AccountsRoute::AddAccount => None, + crate::accounts::AccountsRoute::Onboarding => { + Some(FollowPackOnboardingView::scroll_id()) + } }, Route::Reply(note_id) => Some(PostReplyView::scroll_id(col, note_id.bytes())), Route::Quote(note_id) => Some(QuoteRepostView::scroll_id(col, note_id.bytes())), @@ -1085,6 +1108,7 @@ fn route_uses_frame(route: &Route) -> bool { Route::Accounts(accounts_route) => match accounts_route { crate::accounts::AccountsRoute::Accounts => true, crate::accounts::AccountsRoute::AddAccount => false, + crate::accounts::AccountsRoute::Onboarding => false, }, Route::Relays => true, Route::Timeline(_) => false, diff --git a/crates/notedeck_columns/src/profile.rs b/crates/notedeck_columns/src/profile.rs index 5d068bf..7d9fc57 100644 --- a/crates/notedeck_columns/src/profile.rs +++ b/crates/notedeck_columns/src/profile.rs @@ -218,18 +218,30 @@ fn send_note_builder(builder: NoteBuilder, ndb: &Ndb, pool: &mut RelayPool, kp: pool.send(event); } -pub fn send_new_contact_list(kp: FilledKeypair, ndb: &Ndb, pool: &mut RelayPool) { - let builder = construct_new_contact_list(kp.pubkey); +pub fn send_new_contact_list( + kp: FilledKeypair, + ndb: &Ndb, + pool: &mut RelayPool, + mut pks_to_follow: Vec, +) { + if !pks_to_follow.contains(kp.pubkey) { + pks_to_follow.push(*kp.pubkey); + } + + let builder = construct_new_contact_list(pks_to_follow); send_note_builder(builder, ndb, pool, kp); } -fn construct_new_contact_list<'a>(pk: &'a Pubkey) -> NoteBuilder<'a> { - NoteBuilder::new() +fn construct_new_contact_list<'a>(pks: Vec) -> NoteBuilder<'a> { + let mut builder = NoteBuilder::new() .content("") .kind(3) - .options(NoteBuildOptions::default()) - .start_tag() - .tag_str("p") - .tag_str(&pk.hex()) + .options(NoteBuildOptions::default()); + + for pk in pks { + builder = builder.start_tag().tag_str("p").tag_str(&pk.hex()); + } + + builder } diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs index 3c19def..363499e 100644 --- a/crates/notedeck_columns/src/route.rs +++ b/crates/notedeck_columns/src/route.rs @@ -278,6 +278,11 @@ impl Route { "Add Account", "Column title for adding new account" )), + AccountsRoute::Onboarding => ColumnTitle::formatted(tr!( + i18n, + "Onboarding", + "Column title for finding users to follow" + )), }, Route::ComposeNote => ColumnTitle::formatted(tr!( i18n, diff --git a/crates/notedeck_columns/src/ui/account_login_view.rs b/crates/notedeck_columns/src/ui/account_login_view.rs index 73b26df..bfa60db 100644 --- a/crates/notedeck_columns/src/ui/account_login_view.rs +++ b/crates/notedeck_columns/src/ui/account_login_view.rs @@ -1,4 +1,5 @@ use crate::login_manager::AcquireKeyState; +use crate::ui::onboarding::FollowPacksResponse; use crate::ui::{Preview, PreviewConfig}; use egui::{ Align, Button, Color32, Frame, InnerResponse, Layout, Margin, RichText, TextEdit, Vec2, @@ -18,7 +19,8 @@ pub struct AccountLoginView<'a> { } pub enum AccountLoginResponse { - CreateNew, + CreatingNew, + Onboarding(FollowPacksResponse), LoginWith(Keypair), } @@ -96,7 +98,7 @@ impl<'a> AccountLoginView<'a> { }); if self.manager.check_for_create_new() { - return Some(AccountLoginResponse::CreateNew); + return Some(AccountLoginResponse::CreatingNew); } if let Some(keypair) = self.manager.get_login_keypair() {