notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 0ac131ef06ce4d06113d81868deb24af8aa7614d
parent 409e8c2e3a890b5b4d7553c023ad0144e842733a
Author: kernelkind <kernelkind@gmail.com>
Date:   Tue, 26 Nov 2024 23:40:33 -0500

ui: update account management to design

Closes: https://github.com/damus-io/notedeck/issues/486
Fixes: https://github.com/damus-io/notedeck/issues/444
Signed-off-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Msrc/accounts/mod.rs | 11++++++++++-
Msrc/app_style.rs | 6++++++
Msrc/colors.rs | 8++++----
Msrc/ui/accounts.rs | 108++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/ui/profile/preview.rs | 52++++++++++++++++++++++++++++++++++++++++------------
5 files changed, 106 insertions(+), 79 deletions(-)

diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs @@ -131,7 +131,16 @@ impl Accounts { self.select_account(selected_index - 1); } Ordering::Equal => { - self.clear_selected_account(); + if self.accounts.is_empty() { + // If no accounts remain, clear the selection + self.clear_selected_account(); + } else if index >= self.accounts.len() { + // If the removed account was the last one, select the new last account + self.select_account(self.accounts.len() - 1); + } else { + // Otherwise, select the account at the same position + self.select_account(index); + } } Ordering::Less => {} } diff --git a/src/app_style.rs b/src/app_style.rs @@ -81,6 +81,7 @@ pub fn desktop_font_size(text_style: &NotedeckTextStyle) -> f32 { NotedeckTextStyle::Monospace => 13.0, NotedeckTextStyle::Button => 13.0, NotedeckTextStyle::Small => 12.0, + NotedeckTextStyle::Tiny => 11.0, } } @@ -94,6 +95,7 @@ pub fn mobile_font_size(text_style: &NotedeckTextStyle) -> f32 { NotedeckTextStyle::Monospace => 13.0, NotedeckTextStyle::Button => 13.0, NotedeckTextStyle::Small => 12.0, + NotedeckTextStyle::Tiny => 11.0, } } @@ -114,6 +116,7 @@ pub enum NotedeckTextStyle { Monospace, Button, Small, + Tiny, } impl NotedeckTextStyle { @@ -126,6 +129,7 @@ impl NotedeckTextStyle { Self::Monospace => TextStyle::Monospace, Self::Button => TextStyle::Button, Self::Small => TextStyle::Small, + Self::Tiny => TextStyle::Name("Tiny".into()), } } @@ -138,6 +142,7 @@ impl NotedeckTextStyle { Self::Monospace => FontFamily::Monospace, Self::Button => FontFamily::Proportional, Self::Small => FontFamily::Proportional, + Self::Tiny => FontFamily::Proportional, } } } @@ -154,6 +159,7 @@ pub fn create_themed_visuals(theme: ColorTheme, default: Visuals) -> Visuals { color: theme.selection_color, }, }, + warn_fg_color: theme.warn_fg_color, widgets: Widgets { noninteractive: WidgetVisuals { bg_fill: theme.noninteractive_bg_fill, diff --git a/src/colors.rs b/src/colors.rs @@ -8,7 +8,7 @@ pub const PINK: Color32 = Color32::from_rgb(0xE4, 0x5A, 0xC9); pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A); const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00); const RED_700: Color32 = Color32::from_rgb(0xC7, 0x37, 0x5A); -//const ORANGE_700: Color32 = Color32::from_rgb(0xF6, 0xB1, 0x4A); +const ORANGE_700: Color32 = Color32::from_rgb(0xF6, 0xB1, 0x4A); // BACKGROUNDS const SEMI_DARKER_BG: Color32 = Color32::from_rgb(0x39, 0x39, 0x39); @@ -30,7 +30,7 @@ pub struct ColorTheme { pub extreme_bg_color: Color32, pub text_color: Color32, pub err_fg_color: Color32, - //pub warn_fg_color: Color32, + pub warn_fg_color: Color32, pub hyperlink_color: Color32, pub selection_color: Color32, @@ -57,7 +57,7 @@ pub fn desktop_dark_color_theme() -> ColorTheme { extreme_bg_color: DARK_ISH_BG, text_color: Color32::WHITE, err_fg_color: RED_700, - //warn_fg_color: ORANGE_700, + warn_fg_color: ORANGE_700, hyperlink_color: PURPLE, selection_color: PURPLE_ALT, @@ -93,7 +93,7 @@ pub fn light_color_theme() -> ColorTheme { extreme_bg_color: LIGHTER_GRAY, text_color: BLACK, err_fg_color: RED_700, - //warn_fg_color: ORANGE_700, + warn_fg_color: ORANGE_700, hyperlink_color: PURPLE, selection_color: PURPLE_ALT, diff --git a/src/ui/accounts.rs b/src/ui/accounts.rs @@ -1,4 +1,4 @@ -use crate::colors::PINK; +use crate::colors::{self, PINK}; use crate::imgcache::ImageCache; use crate::{ accounts::Accounts, @@ -6,7 +6,9 @@ use crate::{ ui::{Preview, PreviewConfig, View}, Damus, }; -use egui::{Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, Vec2}; +use egui::{ + Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Stroke, Ui, Vec2, +}; use nostrdb::{Ndb, Transaction}; use super::profile::preview::SimpleProfilePreview; @@ -25,7 +27,7 @@ pub enum AccountsViewResponse { } #[derive(Debug)] -enum ProfilePreviewOp { +enum ProfilePreviewAction { RemoveAccount, SwitchTo, } @@ -72,14 +74,9 @@ impl<'a> AccountsView<'a> { }; 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 (account_pubkey, has_nsec) = match accounts.get_account(i) { + Some(acc) => (acc.pubkey.bytes(), acc.secret_key.is_some()), + None => continue, }; let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok(); @@ -91,15 +88,22 @@ impl<'a> AccountsView<'a> { }; 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) + let max_size = egui::vec2(ui.available_width(), 77.0); + let resp = ui.allocate_response(max_size, egui::Sense::click()); + ui.allocate_ui_at_rect(resp.rect, |ui| { + let preview = + SimpleProfilePreview::new(profile.as_ref(), img_cache, has_nsec); + show_profile_card(ui, preview, max_size, is_selected, resp) + }) + .inner }; if let Some(op) = profile_peview_view { return_op = Some(match op { - ProfilePreviewOp::SwitchTo => AccountsViewResponse::SelectAccount(i), - ProfilePreviewOp::RemoveAccount => { + ProfilePreviewAction::SwitchTo => { + AccountsViewResponse::SelectAccount(i) + } + ProfilePreviewAction::RemoveAccount => { AccountsViewResponse::RemoveAccount(i) } }); @@ -130,30 +134,36 @@ impl<'a> AccountsView<'a> { fn show_profile_card( ui: &mut egui::Ui, preview: SimpleProfilePreview, - width: f32, + max_size: egui::Vec2, 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() + card_resp: egui::Response, +) -> Option<ProfilePreviewAction> { + let mut op: Option<ProfilePreviewAction> = None; + + ui.add_sized(max_size, |ui: &mut egui::Ui| { + let mut frame = Frame::none(); + if is_selected || card_resp.hovered() { + frame = frame.fill(ui.visuals().noninteractive().weak_bg_fill) + } + if is_selected { + frame = frame.stroke(Stroke::new(2.0, colors::PINK)) + } + frame + .rounding(8.0) + .inner_margin(8.0) .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) - } + if card_resp.clicked() { + op = Some(ProfilePreviewAction::SwitchTo); + } + if ui + .add_sized(egui::Vec2::new(84.0, 32.0), sign_out_button()) + .clicked() + { + op = Some(ProfilePreviewAction::RemoveAccount) } }); }); @@ -183,34 +193,8 @@ fn add_account_button() -> Button<'static> { .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 - } +fn sign_out_button() -> egui::Button<'static> { + egui::Button::new(RichText::new("Sign out")) } // PREVIEWS diff --git a/src/ui/profile/preview.rs b/src/ui/profile/preview.rs @@ -1,11 +1,11 @@ -use crate::app_style::NotedeckTextStyle; +use crate::app_style::{get_font_size, NotedeckTextStyle}; use crate::imgcache::ImageCache; use crate::storage::{DataPath, DataPathType}; use crate::ui::ProfilePic; use crate::user_account::UserAccount; use crate::{colors, images, DisplayName}; use egui::load::TexturePoll; -use egui::{Frame, RichText, Sense, Widget}; +use egui::{Frame, Label, RichText, Sense, Widget}; use egui_extras::Size; use enostr::NoteId; use nostrdb::ProfileRecord; @@ -93,11 +93,20 @@ impl egui::Widget for ProfilePreview<'_, '_> { pub struct SimpleProfilePreview<'a, 'cache> { profile: Option<&'a ProfileRecord<'a>>, cache: &'cache mut ImageCache, + is_nsec: bool, } impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { - pub fn new(profile: Option<&'a ProfileRecord<'a>>, cache: &'cache mut ImageCache) -> Self { - SimpleProfilePreview { profile, cache } + pub fn new( + profile: Option<&'a ProfileRecord<'a>>, + cache: &'cache mut ImageCache, + is_nsec: bool, + ) -> Self { + SimpleProfilePreview { + profile, + cache, + is_nsec, + } } } @@ -108,6 +117,16 @@ impl egui::Widget for SimpleProfilePreview<'_, '_> { ui.add(ProfilePic::new(self.cache, get_profile_url(self.profile)).size(48.0)); ui.vertical(|ui| { ui.add(display_name_widget(get_display_name(self.profile), true)); + if !self.is_nsec { + ui.add( + Label::new( + RichText::new("View only mode") + .size(get_font_size(ui.ctx(), &NotedeckTextStyle::Tiny)) + .color(ui.visuals().warn_fg_color), + ) + .selectable(false), + ); + } }); }) .response @@ -203,8 +222,10 @@ fn display_name_widget( ) -> impl egui::Widget + '_ { move |ui: &mut egui::Ui| match display_name { DisplayName::One(n) => { - let name_response = - ui.label(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style())); + let name_response = ui.add( + Label::new(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style())) + .selectable(false), + ); if add_placeholder_space { ui.add_space(16.0); } @@ -215,14 +236,21 @@ fn display_name_widget( display_name, username, } => { - ui.label( - RichText::new(display_name).text_style(NotedeckTextStyle::Heading3.text_style()), + ui.add( + Label::new( + RichText::new(display_name) + .text_style(NotedeckTextStyle::Heading3.text_style()), + ) + .selectable(false), ); - ui.label( - RichText::new(format!("@{}", username)) - .size(12.0) - .color(colors::MID_GRAY), + ui.add( + Label::new( + RichText::new(format!("@{}", username)) + .size(12.0) + .color(colors::MID_GRAY), + ) + .selectable(false), ) } }