commit 22e67c95ccb7e378b506b48b502116f3cd18d4e0
parent 6545e1ddee7d8a67bc7dabd4525939ce0f4dcad8
Author: William Casarin <jb55@jb55.com>
Date: Mon, 18 Nov 2024 18:03:27 -0800
refactor: rename AccountsManager to Accounts
plz stop with the managers
Diffstat:
13 files changed, 520 insertions(+), 514 deletions(-)
diff --git a/src/account_manager.rs b/src/account_manager.rs
@@ -1,238 +0,0 @@
-use std::cmp::Ordering;
-
-use enostr::{FilledKeypair, FullKeypair, Keypair};
-use nostrdb::Ndb;
-use serde::{Deserialize, Serialize};
-
-use crate::{
- column::Columns,
- imgcache::ImageCache,
- login_manager::AcquireKeyState,
- route::{Route, Router},
- storage::{KeyStorageResponse, KeyStorageType},
- ui::{
- account_login_view::{AccountLoginResponse, AccountLoginView},
- account_management::{AccountsView, AccountsViewResponse},
- },
- unknowns::SingleUnkIdAction,
-};
-use tracing::{error, info};
-
-pub use crate::user_account::UserAccount;
-
-/// The interface for managing the user's accounts.
-/// Represents all user-facing operations related to account management.
-pub struct AccountManager {
- currently_selected_account: Option<usize>,
- accounts: Vec<UserAccount>,
- key_store: KeyStorageType,
-}
-
-// TODO(jb55): move to accounts/route.rs
-pub enum AccountsRouteResponse {
- Accounts(AccountsViewResponse),
- AddAccount(AccountLoginResponse),
-}
-
-#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
-pub enum AccountsRoute {
- Accounts,
- AddAccount,
-}
-
-/// Render account management views from a route
-#[allow(clippy::too_many_arguments)]
-pub fn render_accounts_route(
- ui: &mut egui::Ui,
- ndb: &Ndb,
- col: usize,
- columns: &mut Columns,
- img_cache: &mut ImageCache,
- accounts: &mut AccountManager,
- login_state: &mut AcquireKeyState,
- route: AccountsRoute,
-) -> SingleUnkIdAction {
- let router = columns.column_mut(col).router_mut();
- let resp = match route {
- AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache)
- .ui(ui)
- .inner
- .map(AccountsRouteResponse::Accounts),
-
- AccountsRoute::AddAccount => AccountLoginView::new(login_state)
- .ui(ui)
- .inner
- .map(AccountsRouteResponse::AddAccount),
- };
-
- if let Some(resp) = resp {
- match resp {
- AccountsRouteResponse::Accounts(response) => {
- process_accounts_view_response(accounts, response, router);
- SingleUnkIdAction::no_action()
- }
- AccountsRouteResponse::AddAccount(response) => {
- let action = process_login_view_response(accounts, response);
- *login_state = Default::default();
- router.go_back();
- action
- }
- }
- } else {
- SingleUnkIdAction::no_action()
- }
-}
-
-pub fn process_accounts_view_response(
- manager: &mut AccountManager,
- response: AccountsViewResponse,
- router: &mut Router<Route>,
-) {
- match response {
- AccountsViewResponse::RemoveAccount(index) => {
- manager.remove_account(index);
- }
- AccountsViewResponse::SelectAccount(index) => {
- manager.select_account(index);
- }
- AccountsViewResponse::RouteToLogin => {
- router.route_to(Route::add_account());
- }
- }
-}
-
-impl AccountManager {
- pub fn new(key_store: KeyStorageType) -> Self {
- let accounts = if let KeyStorageResponse::ReceivedResult(res) = key_store.get_keys() {
- res.unwrap_or_default()
- } else {
- Vec::new()
- };
-
- let currently_selected_account = get_selected_index(&accounts, &key_store);
- AccountManager {
- currently_selected_account,
- accounts,
- key_store,
- }
- }
-
- pub fn get_accounts(&self) -> &Vec<UserAccount> {
- &self.accounts
- }
-
- pub fn get_account(&self, ind: usize) -> Option<&UserAccount> {
- self.accounts.get(ind)
- }
-
- pub fn find_account(&self, pk: &[u8; 32]) -> Option<&UserAccount> {
- self.accounts.iter().find(|acc| acc.pubkey.bytes() == pk)
- }
-
- pub fn remove_account(&mut self, index: usize) {
- if let Some(account) = self.accounts.get(index) {
- let _ = self.key_store.remove_key(account);
- self.accounts.remove(index);
-
- if let Some(selected_index) = self.currently_selected_account {
- match selected_index.cmp(&index) {
- Ordering::Greater => {
- self.select_account(selected_index - 1);
- }
- Ordering::Equal => {
- self.clear_selected_account();
- }
- Ordering::Less => {}
- }
- }
- }
- }
-
- pub fn has_account_pubkey(&self, pubkey: &[u8; 32]) -> bool {
- for account in &self.accounts {
- if account.pubkey.bytes() == pubkey {
- return true;
- }
- }
-
- false
- }
-
- #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
- pub fn add_account(&mut self, account: Keypair) -> SingleUnkIdAction {
- if self.has_account_pubkey(account.pubkey.bytes()) {
- info!("already have account, not adding {}", account.pubkey);
- return SingleUnkIdAction::pubkey(account.pubkey);
- }
-
- let _ = self.key_store.add_key(&account);
- let pk = account.pubkey;
- self.accounts.push(account);
- SingleUnkIdAction::pubkey(pk)
- }
-
- pub fn num_accounts(&self) -> usize {
- self.accounts.len()
- }
-
- pub fn get_selected_account_index(&self) -> Option<usize> {
- self.currently_selected_account
- }
-
- pub fn selected_or_first_nsec(&self) -> Option<FilledKeypair<'_>> {
- self.get_selected_account()
- .and_then(|kp| kp.to_full())
- .or_else(|| self.accounts.iter().find_map(|a| a.to_full()))
- }
-
- pub fn get_selected_account(&self) -> Option<&UserAccount> {
- if let Some(account_index) = self.currently_selected_account {
- if let Some(account) = self.get_account(account_index) {
- Some(account)
- } else {
- None
- }
- } else {
- None
- }
- }
-
- pub fn select_account(&mut self, index: usize) {
- if let Some(account) = self.accounts.get(index) {
- self.currently_selected_account = Some(index);
- self.key_store.select_key(Some(account.pubkey));
- }
- }
-
- pub fn clear_selected_account(&mut self) {
- self.currently_selected_account = None;
- self.key_store.select_key(None);
- }
-}
-
-fn get_selected_index(accounts: &[UserAccount], keystore: &KeyStorageType) -> Option<usize> {
- match keystore.get_selected_key() {
- KeyStorageResponse::ReceivedResult(Ok(Some(pubkey))) => {
- return accounts.iter().position(|account| account.pubkey == pubkey);
- }
-
- KeyStorageResponse::ReceivedResult(Err(e)) => error!("Error getting selected key: {}", e),
- KeyStorageResponse::Waiting | KeyStorageResponse::ReceivedResult(Ok(None)) => {}
- };
-
- None
-}
-
-pub fn process_login_view_response(
- manager: &mut AccountManager,
- response: AccountLoginResponse,
-) -> SingleUnkIdAction {
- let r = match response {
- AccountLoginResponse::CreateNew => {
- manager.add_account(FullKeypair::generate().to_keypair())
- }
- AccountLoginResponse::LoginWith(keypair) => manager.add_account(keypair),
- };
- manager.select_account(manager.num_accounts() - 1);
- r
-}
diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs
@@ -0,0 +1,228 @@
+use std::cmp::Ordering;
+
+use enostr::{FilledKeypair, FullKeypair, Keypair};
+use nostrdb::Ndb;
+
+use crate::{
+ column::Columns,
+ imgcache::ImageCache,
+ login_manager::AcquireKeyState,
+ route::{Route, Router},
+ storage::{KeyStorageResponse, KeyStorageType},
+ ui::{
+ account_login_view::{AccountLoginResponse, AccountLoginView},
+ accounts::{AccountsView, AccountsViewResponse},
+ },
+ unknowns::SingleUnkIdAction,
+ user_account::UserAccount,
+};
+use tracing::{error, info};
+
+mod route;
+
+pub use route::{AccountsRoute, AccountsRouteResponse};
+
+/// The interface for managing the user's accounts.
+/// Represents all user-facing operations related to account management.
+pub struct Accounts {
+ currently_selected_account: Option<usize>,
+ accounts: Vec<UserAccount>,
+ key_store: KeyStorageType,
+}
+
+/// Render account management views from a route
+#[allow(clippy::too_many_arguments)]
+pub fn render_accounts_route(
+ ui: &mut egui::Ui,
+ ndb: &Ndb,
+ col: usize,
+ columns: &mut Columns,
+ img_cache: &mut ImageCache,
+ accounts: &mut Accounts,
+ login_state: &mut AcquireKeyState,
+ route: AccountsRoute,
+) -> SingleUnkIdAction {
+ let router = columns.column_mut(col).router_mut();
+ let resp = match route {
+ AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache)
+ .ui(ui)
+ .inner
+ .map(AccountsRouteResponse::Accounts),
+
+ AccountsRoute::AddAccount => AccountLoginView::new(login_state)
+ .ui(ui)
+ .inner
+ .map(AccountsRouteResponse::AddAccount),
+ };
+
+ if let Some(resp) = resp {
+ match resp {
+ AccountsRouteResponse::Accounts(response) => {
+ process_accounts_view_response(accounts, response, router);
+ SingleUnkIdAction::no_action()
+ }
+ AccountsRouteResponse::AddAccount(response) => {
+ let action = process_login_view_response(accounts, response);
+ *login_state = Default::default();
+ router.go_back();
+ action
+ }
+ }
+ } else {
+ SingleUnkIdAction::no_action()
+ }
+}
+
+pub fn process_accounts_view_response(
+ manager: &mut Accounts,
+ response: AccountsViewResponse,
+ router: &mut Router<Route>,
+) {
+ match response {
+ AccountsViewResponse::RemoveAccount(index) => {
+ manager.remove_account(index);
+ }
+ AccountsViewResponse::SelectAccount(index) => {
+ manager.select_account(index);
+ }
+ AccountsViewResponse::RouteToLogin => {
+ router.route_to(Route::add_account());
+ }
+ }
+}
+
+impl Accounts {
+ pub fn new(key_store: KeyStorageType) -> Self {
+ let accounts = if let KeyStorageResponse::ReceivedResult(res) = key_store.get_keys() {
+ res.unwrap_or_default()
+ } else {
+ Vec::new()
+ };
+
+ let currently_selected_account = get_selected_index(&accounts, &key_store);
+ Accounts {
+ currently_selected_account,
+ accounts,
+ key_store,
+ }
+ }
+
+ pub fn get_accounts(&self) -> &Vec<UserAccount> {
+ &self.accounts
+ }
+
+ pub fn get_account(&self, ind: usize) -> Option<&UserAccount> {
+ self.accounts.get(ind)
+ }
+
+ pub fn find_account(&self, pk: &[u8; 32]) -> Option<&UserAccount> {
+ self.accounts.iter().find(|acc| acc.pubkey.bytes() == pk)
+ }
+
+ pub fn remove_account(&mut self, index: usize) {
+ if let Some(account) = self.accounts.get(index) {
+ let _ = self.key_store.remove_key(account);
+ self.accounts.remove(index);
+
+ if let Some(selected_index) = self.currently_selected_account {
+ match selected_index.cmp(&index) {
+ Ordering::Greater => {
+ self.select_account(selected_index - 1);
+ }
+ Ordering::Equal => {
+ self.clear_selected_account();
+ }
+ Ordering::Less => {}
+ }
+ }
+ }
+ }
+
+ pub fn has_account_pubkey(&self, pubkey: &[u8; 32]) -> bool {
+ for account in &self.accounts {
+ if account.pubkey.bytes() == pubkey {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
+ pub fn add_account(&mut self, account: Keypair) -> SingleUnkIdAction {
+ if self.has_account_pubkey(account.pubkey.bytes()) {
+ info!("already have account, not adding {}", account.pubkey);
+ return SingleUnkIdAction::pubkey(account.pubkey);
+ }
+
+ let _ = self.key_store.add_key(&account);
+ let pk = account.pubkey;
+ self.accounts.push(account);
+ SingleUnkIdAction::pubkey(pk)
+ }
+
+ pub fn num_accounts(&self) -> usize {
+ self.accounts.len()
+ }
+
+ pub fn get_selected_account_index(&self) -> Option<usize> {
+ self.currently_selected_account
+ }
+
+ pub fn selected_or_first_nsec(&self) -> Option<FilledKeypair<'_>> {
+ self.get_selected_account()
+ .and_then(|kp| kp.to_full())
+ .or_else(|| self.accounts.iter().find_map(|a| a.to_full()))
+ }
+
+ pub fn get_selected_account(&self) -> Option<&UserAccount> {
+ if let Some(account_index) = self.currently_selected_account {
+ if let Some(account) = self.get_account(account_index) {
+ Some(account)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ pub fn select_account(&mut self, index: usize) {
+ if let Some(account) = self.accounts.get(index) {
+ self.currently_selected_account = Some(index);
+ self.key_store.select_key(Some(account.pubkey));
+ }
+ }
+
+ pub fn clear_selected_account(&mut self) {
+ self.currently_selected_account = None;
+ self.key_store.select_key(None);
+ }
+}
+
+fn get_selected_index(accounts: &[UserAccount], keystore: &KeyStorageType) -> Option<usize> {
+ match keystore.get_selected_key() {
+ KeyStorageResponse::ReceivedResult(Ok(Some(pubkey))) => {
+ return accounts.iter().position(|account| account.pubkey == pubkey);
+ }
+
+ KeyStorageResponse::ReceivedResult(Err(e)) => error!("Error getting selected key: {}", e),
+ KeyStorageResponse::Waiting | KeyStorageResponse::ReceivedResult(Ok(None)) => {}
+ };
+
+ None
+}
+
+pub fn process_login_view_response(
+ manager: &mut Accounts,
+ response: AccountLoginResponse,
+) -> SingleUnkIdAction {
+ let r = match response {
+ AccountLoginResponse::CreateNew => {
+ manager.add_account(FullKeypair::generate().to_keypair())
+ }
+ AccountLoginResponse::LoginWith(keypair) => manager.add_account(keypair),
+ };
+ manager.select_account(manager.num_accounts() - 1);
+ r
+}
diff --git a/src/accounts/route.rs b/src/accounts/route.rs
@@ -0,0 +1,13 @@
+use super::{AccountLoginResponse, AccountsViewResponse};
+use serde::{Deserialize, Serialize};
+
+pub enum AccountsRouteResponse {
+ Accounts(AccountsViewResponse),
+ AddAccount(AccountLoginResponse),
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
+pub enum AccountsRoute {
+ Accounts,
+ AddAccount,
+}
diff --git a/src/app.rs b/src/app.rs
@@ -1,10 +1,10 @@
use crate::{
- account_manager::AccountManager,
+ accounts::{Accounts, AccountsRoute},
app_creation::setup_cc,
app_size_handler::AppSizeHandler,
app_style::user_requested_visuals_change,
args::Args,
- column::Columns,
+ column::{Column, Columns},
draft::Drafts,
filter::FilterState,
frame_history::FrameHistory,
@@ -13,6 +13,7 @@ use crate::{
notecache::NoteCache,
notes_holder::NotesHolderStorage,
profile::Profile,
+ route::Route,
storage::{self, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType},
subscriptions::{SubKind, Subscriptions},
support::Support,
@@ -57,7 +58,7 @@ pub struct Damus {
pub threads: NotesHolderStorage<Thread>,
pub profiles: NotesHolderStorage<Profile>,
pub img_cache: ImageCache,
- pub accounts: AccountManager,
+ pub accounts: Accounts,
pub subscriptions: Subscriptions,
pub app_rect_handler: AppSizeHandler,
pub support: Support,
@@ -415,7 +416,7 @@ impl Damus {
KeyStorageType::None
};
- let mut accounts = AccountManager::new(keystore);
+ let mut accounts = Accounts::new(keystore);
let num_keys = parsed_args.keys.len();
@@ -489,7 +490,9 @@ impl Damus {
let debug = parsed_args.debug;
if columns.columns().is_empty() {
- columns.new_column_picker();
+ columns.add_column(Column::new(vec![Route::Accounts(
+ AccountsRoute::AddAccount,
+ )]));
}
let app_rect_handler = AppSizeHandler::new(&path);
@@ -535,11 +538,11 @@ impl Damus {
&mut self.img_cache
}
- pub fn accounts(&self) -> &AccountManager {
+ pub fn accounts(&self) -> &Accounts {
&self.accounts
}
- pub fn accounts_mut(&mut self) -> &mut AccountManager {
+ pub fn accounts_mut(&mut self) -> &mut Accounts {
&mut self.accounts
}
@@ -603,7 +606,7 @@ impl Damus {
&config,
)
.expect("ndb"),
- accounts: AccountManager::new(KeyStorageType::None),
+ accounts: Accounts::new(KeyStorageType::None),
frame_history: FrameHistory::default(),
view_state: ViewState::default(),
diff --git a/src/lib.rs b/src/lib.rs
@@ -4,7 +4,7 @@ mod error;
//mod note;
//mod block;
mod abbrev;
-pub mod account_manager;
+pub mod accounts;
mod actionbar;
pub mod app_creation;
mod app_size_handler;
diff --git a/src/nav.rs b/src/nav.rs
@@ -1,5 +1,5 @@
use crate::{
- account_manager::render_accounts_route,
+ accounts::render_accounts_route,
app_style::{get_font_size, NotedeckTextStyle},
column::Columns,
fonts::NamedFontFamily,
diff --git a/src/route.rs b/src/route.rs
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self};
use crate::{
- account_manager::AccountsRoute,
+ accounts::AccountsRoute,
column::Columns,
timeline::{TimelineId, TimelineRoute},
ui::{
diff --git a/src/timeline/route.rs b/src/timeline/route.rs
@@ -1,5 +1,5 @@
use crate::{
- account_manager::AccountManager,
+ accounts::Accounts,
column::Columns,
draft::Drafts,
imgcache::ImageCache,
@@ -51,7 +51,7 @@ pub fn render_timeline_route(
unknown_ids: &mut UnknownIds,
note_cache: &mut NoteCache,
threads: &mut NotesHolderStorage<Thread>,
- accounts: &mut AccountManager,
+ accounts: &mut Accounts,
route: TimelineRoute,
col: usize,
textmode: bool,
diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs
@@ -1,257 +0,0 @@
-use crate::colors::PINK;
-use crate::imgcache::ImageCache;
-use crate::{
- account_manager::AccountManager,
- route::{Route, Router},
- ui::{Preview, PreviewConfig, View},
- Damus,
-};
-use egui::{Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, Vec2};
-use nostrdb::{Ndb, Transaction};
-
-use super::profile::preview::SimpleProfilePreview;
-
-pub struct AccountsView<'a> {
- ndb: &'a Ndb,
- accounts: &'a AccountManager,
- img_cache: &'a mut ImageCache,
-}
-
-#[derive(Clone, Debug)]
-pub enum AccountsViewResponse {
- SelectAccount(usize),
- RemoveAccount(usize),
- RouteToLogin,
-}
-
-#[derive(Debug)]
-enum ProfilePreviewOp {
- RemoveAccount,
- SwitchTo,
-}
-
-impl<'a> AccountsView<'a> {
- pub fn new(ndb: &'a Ndb, accounts: &'a AccountManager, img_cache: &'a mut ImageCache) -> Self {
- AccountsView {
- ndb,
- accounts,
- img_cache,
- }
- }
-
- pub fn ui(&mut self, ui: &mut Ui) -> InnerResponse<Option<AccountsViewResponse>> {
- Frame::none().outer_margin(12.0).show(ui, |ui| {
- if let Some(resp) = Self::top_section_buttons_widget(ui).inner {
- return Some(resp);
- }
-
- ui.add_space(8.0);
- scroll_area()
- .show(ui, |ui| {
- Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache)
- })
- .inner
- })
- }
-
- fn show_accounts(
- ui: &mut Ui,
- account_manager: &AccountManager,
- ndb: &Ndb,
- img_cache: &mut ImageCache,
- ) -> Option<AccountsViewResponse> {
- let mut return_op: Option<AccountsViewResponse> = None;
- ui.allocate_ui_with_layout(
- Vec2::new(ui.available_size_before_wrap().x, 32.0),
- Layout::top_down(egui::Align::Min),
- |ui| {
- let txn = if let Ok(txn) = Transaction::new(ndb) {
- txn
- } else {
- return;
- };
-
- for i in 0..account_manager.num_accounts() {
- let account_pubkey = account_manager
- .get_account(i)
- .map(|account| account.pubkey.bytes());
-
- let account_pubkey = if let Some(pubkey) = account_pubkey {
- pubkey
- } else {
- continue;
- };
-
- let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok();
- let is_selected =
- if let Some(selected) = account_manager.get_selected_account_index() {
- i == selected
- } else {
- false
- };
-
- let profile_peview_view = {
- let width = ui.available_width();
- let preview = SimpleProfilePreview::new(profile.as_ref(), img_cache);
- show_profile_card(ui, preview, width, is_selected)
- };
-
- if let Some(op) = profile_peview_view {
- return_op = Some(match op {
- ProfilePreviewOp::SwitchTo => AccountsViewResponse::SelectAccount(i),
- ProfilePreviewOp::RemoveAccount => {
- AccountsViewResponse::RemoveAccount(i)
- }
- });
- }
- }
- },
- );
- return_op
- }
-
- fn top_section_buttons_widget(
- ui: &mut egui::Ui,
- ) -> InnerResponse<Option<AccountsViewResponse>> {
- ui.allocate_ui_with_layout(
- Vec2::new(ui.available_size_before_wrap().x, 32.0),
- Layout::left_to_right(egui::Align::Center),
- |ui| {
- if ui.add(add_account_button()).clicked() {
- Some(AccountsViewResponse::RouteToLogin)
- } else {
- None
- }
- },
- )
- }
-}
-
-fn show_profile_card(
- ui: &mut egui::Ui,
- preview: SimpleProfilePreview,
- width: f32,
- is_selected: bool,
-) -> Option<ProfilePreviewOp> {
- let mut op: Option<ProfilePreviewOp> = None;
-
- ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| {
- Frame::none()
- .show(ui, |ui| {
- ui.horizontal(|ui| {
- ui.add(preview);
-
- ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
- if is_selected {
- ui.add(selected_widget());
- } else {
- if ui
- .add(switch_button(ui.style().visuals.dark_mode))
- .clicked()
- {
- op = Some(ProfilePreviewOp::SwitchTo);
- }
- if ui.add(sign_out_button(ui)).clicked() {
- op = Some(ProfilePreviewOp::RemoveAccount)
- }
- }
- });
- });
- })
- .response
- });
- ui.add_space(16.0);
- op
-}
-
-fn scroll_area() -> ScrollArea {
- egui::ScrollArea::vertical()
- .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
- .auto_shrink([false; 2])
-}
-
-fn add_account_button() -> Button<'static> {
- let img_data = egui::include_image!("../../assets/icons/add_account_icon_4x.png");
- let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0));
- Button::image_and_text(
- img,
- RichText::new(" Add account")
- .size(16.0)
- // TODO: this color should not be hard coded. Find some way to add it to the visuals
- .color(PINK),
- )
- .frame(false)
-}
-
-fn sign_out_button(ui: &egui::Ui) -> egui::Button<'static> {
- let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png");
- let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0));
-
- egui::Button::image_and_text(
- img,
- RichText::new("Sign out").color(ui.visuals().noninteractive().fg_stroke.color),
- )
- .frame(false)
-}
-
-fn switch_button(dark_mode: bool) -> egui::Button<'static> {
- let _ = dark_mode;
-
- egui::Button::new("Switch").min_size(Vec2::new(76.0, 32.0))
-}
-
-fn selected_widget() -> impl egui::Widget {
- |ui: &mut egui::Ui| {
- Frame::none()
- .show(ui, |ui| {
- ui.label(RichText::new("Selected").size(13.0).color(PINK));
- let img_data = egui::include_image!("../../assets/icons/select_icon_3x.png");
- let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0));
- ui.add(img);
- })
- .response
- }
-}
-
-// PREVIEWS
-mod preview {
-
- use super::*;
- use crate::{account_manager::process_accounts_view_response, test_data};
-
- pub struct AccountsPreview {
- app: Damus,
- router: Router<Route>,
- }
-
- impl AccountsPreview {
- fn new() -> Self {
- let app = test_data::test_app();
- let router = Router::new(vec![Route::accounts()]);
-
- AccountsPreview { app, router }
- }
- }
-
- impl View for AccountsPreview {
- fn ui(&mut self, ui: &mut egui::Ui) {
- ui.add_space(24.0);
- // TODO(jb55): maybe just use render_nav here so we can step through routes
- if let Some(response) =
- AccountsView::new(&self.app.ndb, &self.app.accounts, &mut self.app.img_cache)
- .ui(ui)
- .inner
- {
- process_accounts_view_response(self.app.accounts_mut(), response, &mut self.router);
- }
- }
- }
-
- impl<'a> Preview for AccountsView<'a> {
- type Prev = AccountsPreview;
-
- fn preview(_cfg: PreviewConfig) -> Self::Prev {
- AccountsPreview::new()
- }
- }
-}
diff --git a/src/ui/accounts.rs b/src/ui/accounts.rs
@@ -0,0 +1,257 @@
+use crate::colors::PINK;
+use crate::imgcache::ImageCache;
+use crate::{
+ accounts::Accounts,
+ route::{Route, Router},
+ ui::{Preview, PreviewConfig, View},
+ Damus,
+};
+use egui::{Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, Vec2};
+use nostrdb::{Ndb, Transaction};
+
+use super::profile::preview::SimpleProfilePreview;
+
+pub struct AccountsView<'a> {
+ ndb: &'a Ndb,
+ accounts: &'a Accounts,
+ img_cache: &'a mut ImageCache,
+}
+
+#[derive(Clone, Debug)]
+pub enum AccountsViewResponse {
+ SelectAccount(usize),
+ RemoveAccount(usize),
+ RouteToLogin,
+}
+
+#[derive(Debug)]
+enum ProfilePreviewOp {
+ RemoveAccount,
+ SwitchTo,
+}
+
+impl<'a> AccountsView<'a> {
+ pub fn new(ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut ImageCache) -> Self {
+ AccountsView {
+ ndb,
+ accounts,
+ img_cache,
+ }
+ }
+
+ pub fn ui(&mut self, ui: &mut Ui) -> InnerResponse<Option<AccountsViewResponse>> {
+ Frame::none().outer_margin(12.0).show(ui, |ui| {
+ if let Some(resp) = Self::top_section_buttons_widget(ui).inner {
+ return Some(resp);
+ }
+
+ ui.add_space(8.0);
+ scroll_area()
+ .show(ui, |ui| {
+ Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache)
+ })
+ .inner
+ })
+ }
+
+ fn show_accounts(
+ ui: &mut Ui,
+ accounts: &Accounts,
+ ndb: &Ndb,
+ img_cache: &mut ImageCache,
+ ) -> Option<AccountsViewResponse> {
+ let mut return_op: Option<AccountsViewResponse> = None;
+ ui.allocate_ui_with_layout(
+ Vec2::new(ui.available_size_before_wrap().x, 32.0),
+ Layout::top_down(egui::Align::Min),
+ |ui| {
+ let txn = if let Ok(txn) = Transaction::new(ndb) {
+ txn
+ } else {
+ return;
+ };
+
+ for i in 0..accounts.num_accounts() {
+ let account_pubkey = accounts
+ .get_account(i)
+ .map(|account| account.pubkey.bytes());
+
+ let account_pubkey = if let Some(pubkey) = account_pubkey {
+ pubkey
+ } else {
+ continue;
+ };
+
+ let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok();
+ let is_selected = if let Some(selected) = accounts.get_selected_account_index()
+ {
+ i == selected
+ } else {
+ false
+ };
+
+ let profile_peview_view = {
+ let width = ui.available_width();
+ let preview = SimpleProfilePreview::new(profile.as_ref(), img_cache);
+ show_profile_card(ui, preview, width, is_selected)
+ };
+
+ if let Some(op) = profile_peview_view {
+ return_op = Some(match op {
+ ProfilePreviewOp::SwitchTo => AccountsViewResponse::SelectAccount(i),
+ ProfilePreviewOp::RemoveAccount => {
+ AccountsViewResponse::RemoveAccount(i)
+ }
+ });
+ }
+ }
+ },
+ );
+ return_op
+ }
+
+ fn top_section_buttons_widget(
+ ui: &mut egui::Ui,
+ ) -> InnerResponse<Option<AccountsViewResponse>> {
+ ui.allocate_ui_with_layout(
+ Vec2::new(ui.available_size_before_wrap().x, 32.0),
+ Layout::left_to_right(egui::Align::Center),
+ |ui| {
+ if ui.add(add_account_button()).clicked() {
+ Some(AccountsViewResponse::RouteToLogin)
+ } else {
+ None
+ }
+ },
+ )
+ }
+}
+
+fn show_profile_card(
+ ui: &mut egui::Ui,
+ preview: SimpleProfilePreview,
+ width: f32,
+ is_selected: bool,
+) -> Option<ProfilePreviewOp> {
+ let mut op: Option<ProfilePreviewOp> = None;
+
+ ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| {
+ Frame::none()
+ .show(ui, |ui| {
+ ui.horizontal(|ui| {
+ ui.add(preview);
+
+ ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
+ if is_selected {
+ ui.add(selected_widget());
+ } else {
+ if ui
+ .add(switch_button(ui.style().visuals.dark_mode))
+ .clicked()
+ {
+ op = Some(ProfilePreviewOp::SwitchTo);
+ }
+ if ui.add(sign_out_button(ui)).clicked() {
+ op = Some(ProfilePreviewOp::RemoveAccount)
+ }
+ }
+ });
+ });
+ })
+ .response
+ });
+ ui.add_space(16.0);
+ op
+}
+
+fn scroll_area() -> ScrollArea {
+ egui::ScrollArea::vertical()
+ .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
+ .auto_shrink([false; 2])
+}
+
+fn add_account_button() -> Button<'static> {
+ let img_data = egui::include_image!("../../assets/icons/add_account_icon_4x.png");
+ let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0));
+ Button::image_and_text(
+ img,
+ RichText::new(" Add account")
+ .size(16.0)
+ // TODO: this color should not be hard coded. Find some way to add it to the visuals
+ .color(PINK),
+ )
+ .frame(false)
+}
+
+fn sign_out_button(ui: &egui::Ui) -> egui::Button<'static> {
+ let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png");
+ let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0));
+
+ egui::Button::image_and_text(
+ img,
+ RichText::new("Sign out").color(ui.visuals().noninteractive().fg_stroke.color),
+ )
+ .frame(false)
+}
+
+fn switch_button(dark_mode: bool) -> egui::Button<'static> {
+ let _ = dark_mode;
+
+ egui::Button::new("Switch").min_size(Vec2::new(76.0, 32.0))
+}
+
+fn selected_widget() -> impl egui::Widget {
+ |ui: &mut egui::Ui| {
+ Frame::none()
+ .show(ui, |ui| {
+ ui.label(RichText::new("Selected").size(13.0).color(PINK));
+ let img_data = egui::include_image!("../../assets/icons/select_icon_3x.png");
+ let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0));
+ ui.add(img);
+ })
+ .response
+ }
+}
+
+// PREVIEWS
+mod preview {
+
+ use super::*;
+ use crate::{accounts::process_accounts_view_response, test_data};
+
+ pub struct AccountsPreview {
+ app: Damus,
+ router: Router<Route>,
+ }
+
+ impl AccountsPreview {
+ fn new() -> Self {
+ let app = test_data::test_app();
+ let router = Router::new(vec![Route::accounts()]);
+
+ AccountsPreview { app, router }
+ }
+ }
+
+ impl View for AccountsPreview {
+ fn ui(&mut self, ui: &mut egui::Ui) {
+ ui.add_space(24.0);
+ // TODO(jb55): maybe just use render_nav here so we can step through routes
+ if let Some(response) =
+ AccountsView::new(&self.app.ndb, &self.app.accounts, &mut self.app.img_cache)
+ .ui(ui)
+ .inner
+ {
+ process_accounts_view_response(self.app.accounts_mut(), response, &mut self.router);
+ }
+ }
+ }
+
+ impl<'a> Preview for AccountsView<'a> {
+ type Prev = AccountsPreview;
+
+ fn preview(_cfg: PreviewConfig) -> Self::Prev {
+ AccountsPreview::new()
+ }
+ }
+}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
@@ -1,5 +1,5 @@
pub mod account_login_view;
-pub mod account_management;
+pub mod accounts;
pub mod add_column;
pub mod anim;
pub mod mention;
@@ -13,7 +13,7 @@ pub mod thread;
pub mod timeline;
pub mod username;
-pub use account_management::AccountsView;
+pub use accounts::AccountsView;
pub use mention::Mention;
pub use note::{NoteResponse, NoteView, PostReplyView, PostView};
pub use preview::{Preview, PreviewApp, PreviewConfig};
diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs
@@ -2,7 +2,7 @@ use egui::{vec2, Color32, InnerResponse, Layout, Margin, Separator, Stroke, Widg
use tracing::info;
use crate::{
- account_manager::AccountsRoute,
+ accounts::AccountsRoute,
colors,
column::{Column, Columns},
imgcache::ImageCache,
diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs
@@ -1,7 +1,7 @@
use notedeck::ui::{
- account_login_view::AccountLoginView, account_management::AccountsView,
- add_column::AddColumnView, DesktopSidePanel, PostView, Preview, PreviewApp, PreviewConfig,
- ProfilePic, ProfilePreview, RelayView,
+ account_login_view::AccountLoginView, accounts::AccountsView, add_column::AddColumnView,
+ DesktopSidePanel, PostView, Preview, PreviewApp, PreviewConfig, ProfilePic, ProfilePreview,
+ RelayView,
};
use notedeck::{
app_creation::{generate_mobile_emulator_native_options, generate_native_options, setup_cc},