integrate onboarding

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-08-07 17:35:29 -04:00
parent bdcd31cda0
commit 1566cd5cf4
6 changed files with 192 additions and 85 deletions

View File

@@ -1,15 +1,19 @@
use enostr::{FullKeypair, Pubkey}; use enostr::{FullKeypair, Pubkey};
use nostrdb::{Ndb, Transaction}; 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::app::get_active_columns_mut;
use crate::decks::DecksCache; use crate::decks::DecksCache;
use crate::onboarding::Onboarding;
use crate::profile::send_new_contact_list; use crate::profile::send_new_contact_list;
use crate::subscriptions::Subscriptions;
use crate::ui::onboarding::{FollowPackOnboardingView, FollowPacksResponse, OnboardingResponse};
use crate::{ use crate::{
login_manager::AcquireKeyState, login_manager::AcquireKeyState,
route::Route, route::Route,
timeline::TimelineCache,
ui::{ ui::{
account_login_view::{AccountLoginResponse, AccountLoginView}, account_login_view::{AccountLoginResponse, AccountLoginView},
accounts::{AccountsView, AccountsViewResponse}, accounts::{AccountsView, AccountsViewResponse},
@@ -37,6 +41,7 @@ pub struct SwitchAccountAction {
/// The account to switch to /// The account to switch to
pub switch_to: Pubkey, pub switch_to: Pubkey,
pub switching_to_new: bool,
} }
impl SwitchAccountAction { impl SwitchAccountAction {
@@ -44,8 +49,14 @@ impl SwitchAccountAction {
SwitchAccountAction { SwitchAccountAction {
source_column, source_column,
switch_to, switch_to,
switching_to_new: false,
} }
} }
pub fn switching_to_new(mut self) -> Self {
self.switching_to_new = true;
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -65,13 +76,13 @@ pub struct AddAccountAction {
pub fn render_accounts_route( pub fn render_accounts_route(
ui: &mut egui::Ui, ui: &mut egui::Ui,
app_ctx: &mut AppContext, app_ctx: &mut AppContext,
col: usize, jobs: &mut JobsCache,
decks: &mut DecksCache,
timeline_cache: &mut TimelineCache,
login_state: &mut AcquireKeyState, login_state: &mut AcquireKeyState,
onboarding: &Onboarding,
follow_packs_ui: &mut Nip51SetUiCache,
route: AccountsRoute, route: AccountsRoute,
) -> AddAccountAction { ) -> Option<AccountsResponse> {
let resp = match route { match route {
AccountsRoute::Accounts => AccountsView::new( AccountsRoute::Accounts => AccountsView::new(
app_ctx.ndb, app_ctx.ndb,
app_ctx.accounts, app_ctx.accounts,
@@ -80,47 +91,33 @@ pub fn render_accounts_route(
) )
.ui(ui) .ui(ui)
.inner .inner
.map(AccountsRouteResponse::Accounts), .map(AccountsRouteResponse::Accounts)
.map(AccountsResponse::Account),
AccountsRoute::AddAccount => { AccountsRoute::AddAccount => {
AccountLoginView::new(login_state, app_ctx.clipboard, app_ctx.i18n) AccountLoginView::new(login_state, app_ctx.clipboard, app_ctx.i18n)
.ui(ui) .ui(ui)
.inner .inner
.map(AccountsRouteResponse::AddAccount) .map(AccountsRouteResponse::AddAccount)
.map(AccountsResponse::Account)
} }
}; AccountsRoute::Onboarding => FollowPackOnboardingView::new(
onboarding,
if let Some(resp) = resp { follow_packs_ui,
match resp { app_ctx.ndb,
AccountsRouteResponse::Accounts(response) => { app_ctx.img_cache,
let action = process_accounts_view_response(
app_ctx.i18n, app_ctx.i18n,
app_ctx.accounts, app_ctx.job_pool,
decks, jobs,
col, )
response, .ui(ui)
); .map(|r| match r {
AddAccountAction { OnboardingResponse::FollowPacks(follow_packs_response) => {
accounts_action: action, AccountsResponse::Account(AccountsRouteResponse::AddAccount(
unk_id_action: SingleUnkIdAction::no_action(), 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( pub fn process_login_view_response(
app_ctx: &mut AppContext, app_ctx: &mut AppContext,
timeline_cache: &mut TimelineCache,
decks: &mut DecksCache, decks: &mut DecksCache,
subs: &mut Subscriptions,
onboarding: &mut Onboarding,
col: usize, col: usize,
response: AccountLoginResponse, response: AccountLoginResponse,
) -> AddAccountAction { ) -> AddAccountAction {
let (r, pubkey) = match response { let cur_router = get_active_columns_mut(app_ctx.i18n, app_ctx.accounts, decks)
AccountLoginResponse::CreateNew => { .column_mut(col)
let kp = FullKeypair::generate(); .router_mut();
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)
}
};
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 { if let Some(action) = r {
AddAccountAction { AddAccountAction {
accounts_action: Some(AccountsAction::Switch(SwitchAccountAction { accounts_action: Some(AccountsAction::Switch(SwitchAccountAction {
source_column: col, source_column: col,
switch_to: action.switch_to, switch_to: action.switch_to,
switching_to_new: true,
})), })),
unk_id_action: action.unk_id_action, 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
}
}
}
}

View File

@@ -7,10 +7,16 @@ pub enum AccountsRouteResponse {
AddAccount(AccountLoginResponse), AddAccount(AccountLoginResponse),
} }
pub enum AccountsResponse {
ViewProfile(enostr::Pubkey),
Account(AccountsRouteResponse),
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub enum AccountsRoute { pub enum AccountsRoute {
Accounts, Accounts,
AddAccount, AddAccount,
Onboarding,
} }
impl AccountsRoute { impl AccountsRoute {
@@ -19,6 +25,7 @@ impl AccountsRoute {
match self { match self {
Self::Accounts => &["accounts", "show"], Self::Accounts => &["accounts", "show"],
Self::AddAccount => &["accounts", "new"], Self::AddAccount => &["accounts", "new"],
Self::Onboarding => &["accounts", "onboarding"],
} }
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::{ use crate::{
accounts::{render_accounts_route, AccountsAction}, accounts::{render_accounts_route, AccountsAction, AccountsResponse},
app::{get_active_columns_mut, get_decks_mut}, app::{get_active_columns_mut, get_decks_mut},
column::ColumnsAction, column::ColumnsAction,
deck_state::DeckState, deck_state::DeckState,
@@ -19,6 +19,7 @@ use crate::{
configure_deck::ConfigureDeckView, configure_deck::ConfigureDeckView,
edit_deck::{EditDeckResponse, EditDeckView}, edit_deck::{EditDeckResponse, EditDeckView},
note::{custom_zap::CustomZapView, NewPostAction, PostAction, PostType, QuoteRepostView}, note::{custom_zap::CustomZapView, NewPostAction, PostAction, PostType, QuoteRepostView},
onboarding::FollowPackOnboardingView,
profile::EditProfileView, profile::EditProfileView,
search::{FocusState, SearchView}, search::{FocusState, SearchView},
settings::SettingsAction, settings::SettingsAction,
@@ -85,6 +86,7 @@ impl SwitchingAction {
match &self { match &self {
SwitchingAction::Accounts(account_action) => match account_action { SwitchingAction::Accounts(account_action) => match account_action {
AccountsAction::Switch(switch_action) => { AccountsAction::Switch(switch_action) => {
{
let txn = Transaction::new(ctx.ndb).expect("txn"); let txn = Transaction::new(ctx.ndb).expect("txn");
ctx.accounts.select_account( ctx.accounts.select_account(
&switch_action.switch_to, &switch_action.switch_to,
@@ -93,6 +95,12 @@ impl SwitchingAction {
ctx.pool, ctx.pool,
ui_ctx, ui_ctx,
); );
}
if switch_action.switching_to_new {
decks_cache.add_deck_default(ctx, timeline_cache, switch_action.switch_to);
}
// pop nav after switch // pop nav after switch
get_active_columns_mut(ctx.i18n, ctx.accounts, decks_cache) get_active_columns_mut(ctx.i18n, ctx.accounts, decks_cache)
.column_mut(switch_action.source_column) .column_mut(switch_action.source_column)
@@ -564,22 +572,34 @@ fn render_nav_body(
&mut note_context, &mut note_context,
&mut app.jobs, &mut app.jobs,
), ),
Route::Accounts(amr) => { Route::Accounts(amr) => 's: {
let mut action = render_accounts_route( let Some(action) = render_accounts_route(
ui, ui,
ctx, ctx,
col, &mut app.jobs,
&mut app.decks_cache,
&mut app.timeline_cache,
&mut app.view_state.login, &mut app.view_state.login,
&app.onboarding,
&mut app.view_state.follow_packs,
*amr, *amr,
); ) 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"); let txn = Transaction::new(ctx.ndb).expect("txn");
action.process_action(ctx.unknown_ids, ctx.ndb, &txn); action.process_action(ctx.unknown_ids, ctx.ndb, &txn);
action action
.accounts_action .accounts_action
.map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f))) .map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f)))
} }
}
}
Route::Relays => RelayView::new(ctx.pool, &mut app.view_state.id_string_map, ctx.i18n) Route::Relays => RelayView::new(ctx.pool, &mut app.view_state.id_string_map, ctx.i18n)
.ui(ui) .ui(ui)
.map(RenderNavAction::RelayAction), .map(RenderNavAction::RelayAction),
@@ -1061,6 +1081,9 @@ fn get_scroll_id(
Route::Accounts(accounts_route) => match accounts_route { Route::Accounts(accounts_route) => match accounts_route {
crate::accounts::AccountsRoute::Accounts => Some(AccountsView::scroll_id()), crate::accounts::AccountsRoute::Accounts => Some(AccountsView::scroll_id()),
crate::accounts::AccountsRoute::AddAccount => None, 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::Reply(note_id) => Some(PostReplyView::scroll_id(col, note_id.bytes())),
Route::Quote(note_id) => Some(QuoteRepostView::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 { Route::Accounts(accounts_route) => match accounts_route {
crate::accounts::AccountsRoute::Accounts => true, crate::accounts::AccountsRoute::Accounts => true,
crate::accounts::AccountsRoute::AddAccount => false, crate::accounts::AccountsRoute::AddAccount => false,
crate::accounts::AccountsRoute::Onboarding => false,
}, },
Route::Relays => true, Route::Relays => true,
Route::Timeline(_) => false, Route::Timeline(_) => false,

View File

@@ -218,18 +218,30 @@ fn send_note_builder(builder: NoteBuilder, ndb: &Ndb, pool: &mut RelayPool, kp:
pool.send(event); pool.send(event);
} }
pub fn send_new_contact_list(kp: FilledKeypair, ndb: &Ndb, pool: &mut RelayPool) { pub fn send_new_contact_list(
let builder = construct_new_contact_list(kp.pubkey); kp: FilledKeypair,
ndb: &Ndb,
pool: &mut RelayPool,
mut pks_to_follow: Vec<Pubkey>,
) {
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); send_note_builder(builder, ndb, pool, kp);
} }
fn construct_new_contact_list<'a>(pk: &'a Pubkey) -> NoteBuilder<'a> { fn construct_new_contact_list<'a>(pks: Vec<Pubkey>) -> NoteBuilder<'a> {
NoteBuilder::new() let mut builder = NoteBuilder::new()
.content("") .content("")
.kind(3) .kind(3)
.options(NoteBuildOptions::default()) .options(NoteBuildOptions::default());
.start_tag()
.tag_str("p") for pk in pks {
.tag_str(&pk.hex()) builder = builder.start_tag().tag_str("p").tag_str(&pk.hex());
}
builder
} }

View File

@@ -278,6 +278,11 @@ impl Route {
"Add Account", "Add Account",
"Column title for adding new 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!( Route::ComposeNote => ColumnTitle::formatted(tr!(
i18n, i18n,

View File

@@ -1,4 +1,5 @@
use crate::login_manager::AcquireKeyState; use crate::login_manager::AcquireKeyState;
use crate::ui::onboarding::FollowPacksResponse;
use crate::ui::{Preview, PreviewConfig}; use crate::ui::{Preview, PreviewConfig};
use egui::{ use egui::{
Align, Button, Color32, Frame, InnerResponse, Layout, Margin, RichText, TextEdit, Vec2, Align, Button, Color32, Frame, InnerResponse, Layout, Margin, RichText, TextEdit, Vec2,
@@ -18,7 +19,8 @@ pub struct AccountLoginView<'a> {
} }
pub enum AccountLoginResponse { pub enum AccountLoginResponse {
CreateNew, CreatingNew,
Onboarding(FollowPacksResponse),
LoginWith(Keypair), LoginWith(Keypair),
} }
@@ -96,7 +98,7 @@ impl<'a> AccountLoginView<'a> {
}); });
if self.manager.check_for_create_new() { if self.manager.check_for_create_new() {
return Some(AccountLoginResponse::CreateNew); return Some(AccountLoginResponse::CreatingNew);
} }
if let Some(keypair) = self.manager.get_login_keypair() { if let Some(keypair) = self.manager.get_login_keypair() {