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>
This commit is contained in:
kernelkind
2024-11-26 23:40:33 -05:00
committed by William Casarin
parent 409e8c2e3a
commit 0ac131ef06
5 changed files with 105 additions and 78 deletions

View File

@@ -131,7 +131,16 @@ impl Accounts {
self.select_account(selected_index - 1); self.select_account(selected_index - 1);
} }
Ordering::Equal => { 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 => {} Ordering::Less => {}
} }

View File

@@ -81,6 +81,7 @@ pub fn desktop_font_size(text_style: &NotedeckTextStyle) -> f32 {
NotedeckTextStyle::Monospace => 13.0, NotedeckTextStyle::Monospace => 13.0,
NotedeckTextStyle::Button => 13.0, NotedeckTextStyle::Button => 13.0,
NotedeckTextStyle::Small => 12.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::Monospace => 13.0,
NotedeckTextStyle::Button => 13.0, NotedeckTextStyle::Button => 13.0,
NotedeckTextStyle::Small => 12.0, NotedeckTextStyle::Small => 12.0,
NotedeckTextStyle::Tiny => 11.0,
} }
} }
@@ -114,6 +116,7 @@ pub enum NotedeckTextStyle {
Monospace, Monospace,
Button, Button,
Small, Small,
Tiny,
} }
impl NotedeckTextStyle { impl NotedeckTextStyle {
@@ -126,6 +129,7 @@ impl NotedeckTextStyle {
Self::Monospace => TextStyle::Monospace, Self::Monospace => TextStyle::Monospace,
Self::Button => TextStyle::Button, Self::Button => TextStyle::Button,
Self::Small => TextStyle::Small, Self::Small => TextStyle::Small,
Self::Tiny => TextStyle::Name("Tiny".into()),
} }
} }
@@ -138,6 +142,7 @@ impl NotedeckTextStyle {
Self::Monospace => FontFamily::Monospace, Self::Monospace => FontFamily::Monospace,
Self::Button => FontFamily::Proportional, Self::Button => FontFamily::Proportional,
Self::Small => 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, color: theme.selection_color,
}, },
}, },
warn_fg_color: theme.warn_fg_color,
widgets: Widgets { widgets: Widgets {
noninteractive: WidgetVisuals { noninteractive: WidgetVisuals {
bg_fill: theme.noninteractive_bg_fill, bg_fill: theme.noninteractive_bg_fill,

View File

@@ -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); pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A);
const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00); const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00);
const RED_700: Color32 = Color32::from_rgb(0xC7, 0x37, 0x5A); 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 // BACKGROUNDS
const SEMI_DARKER_BG: Color32 = Color32::from_rgb(0x39, 0x39, 0x39); const SEMI_DARKER_BG: Color32 = Color32::from_rgb(0x39, 0x39, 0x39);
@@ -30,7 +30,7 @@ pub struct ColorTheme {
pub extreme_bg_color: Color32, pub extreme_bg_color: Color32,
pub text_color: Color32, pub text_color: Color32,
pub err_fg_color: Color32, pub err_fg_color: Color32,
//pub warn_fg_color: Color32, pub warn_fg_color: Color32,
pub hyperlink_color: Color32, pub hyperlink_color: Color32,
pub selection_color: Color32, pub selection_color: Color32,
@@ -57,7 +57,7 @@ pub fn desktop_dark_color_theme() -> ColorTheme {
extreme_bg_color: DARK_ISH_BG, extreme_bg_color: DARK_ISH_BG,
text_color: Color32::WHITE, text_color: Color32::WHITE,
err_fg_color: RED_700, err_fg_color: RED_700,
//warn_fg_color: ORANGE_700, warn_fg_color: ORANGE_700,
hyperlink_color: PURPLE, hyperlink_color: PURPLE,
selection_color: PURPLE_ALT, selection_color: PURPLE_ALT,
@@ -93,7 +93,7 @@ pub fn light_color_theme() -> ColorTheme {
extreme_bg_color: LIGHTER_GRAY, extreme_bg_color: LIGHTER_GRAY,
text_color: BLACK, text_color: BLACK,
err_fg_color: RED_700, err_fg_color: RED_700,
//warn_fg_color: ORANGE_700, warn_fg_color: ORANGE_700,
hyperlink_color: PURPLE, hyperlink_color: PURPLE,
selection_color: PURPLE_ALT, selection_color: PURPLE_ALT,

View File

@@ -1,4 +1,4 @@
use crate::colors::PINK; use crate::colors::{self, PINK};
use crate::imgcache::ImageCache; use crate::imgcache::ImageCache;
use crate::{ use crate::{
accounts::Accounts, accounts::Accounts,
@@ -6,7 +6,9 @@ use crate::{
ui::{Preview, PreviewConfig, View}, ui::{Preview, PreviewConfig, View},
Damus, 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 nostrdb::{Ndb, Transaction};
use super::profile::preview::SimpleProfilePreview; use super::profile::preview::SimpleProfilePreview;
@@ -25,7 +27,7 @@ pub enum AccountsViewResponse {
} }
#[derive(Debug)] #[derive(Debug)]
enum ProfilePreviewOp { enum ProfilePreviewAction {
RemoveAccount, RemoveAccount,
SwitchTo, SwitchTo,
} }
@@ -72,14 +74,9 @@ impl<'a> AccountsView<'a> {
}; };
for i in 0..accounts.num_accounts() { for i in 0..accounts.num_accounts() {
let account_pubkey = accounts let (account_pubkey, has_nsec) = match accounts.get_account(i) {
.get_account(i) Some(acc) => (acc.pubkey.bytes(), acc.secret_key.is_some()),
.map(|account| account.pubkey.bytes()); None => continue,
let account_pubkey = if let Some(pubkey) = account_pubkey {
pubkey
} else {
continue;
}; };
let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok(); let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok();
@@ -91,15 +88,22 @@ impl<'a> AccountsView<'a> {
}; };
let profile_peview_view = { let profile_peview_view = {
let width = ui.available_width(); let max_size = egui::vec2(ui.available_width(), 77.0);
let preview = SimpleProfilePreview::new(profile.as_ref(), img_cache); let resp = ui.allocate_response(max_size, egui::Sense::click());
show_profile_card(ui, preview, width, is_selected) 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 { if let Some(op) = profile_peview_view {
return_op = Some(match op { return_op = Some(match op {
ProfilePreviewOp::SwitchTo => AccountsViewResponse::SelectAccount(i), ProfilePreviewAction::SwitchTo => {
ProfilePreviewOp::RemoveAccount => { AccountsViewResponse::SelectAccount(i)
}
ProfilePreviewAction::RemoveAccount => {
AccountsViewResponse::RemoveAccount(i) AccountsViewResponse::RemoveAccount(i)
} }
}); });
@@ -130,30 +134,36 @@ impl<'a> AccountsView<'a> {
fn show_profile_card( fn show_profile_card(
ui: &mut egui::Ui, ui: &mut egui::Ui,
preview: SimpleProfilePreview, preview: SimpleProfilePreview,
width: f32, max_size: egui::Vec2,
is_selected: bool, is_selected: bool,
) -> Option<ProfilePreviewOp> { card_resp: egui::Response,
let mut op: Option<ProfilePreviewOp> = None; ) -> Option<ProfilePreviewAction> {
let mut op: Option<ProfilePreviewAction> = None;
ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| { ui.add_sized(max_size, |ui: &mut egui::Ui| {
Frame::none() 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| { .show(ui, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(preview); ui.add(preview);
ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if is_selected { if card_resp.clicked() {
ui.add(selected_widget()); op = Some(ProfilePreviewAction::SwitchTo);
} else { }
if ui if ui
.add(switch_button(ui.style().visuals.dark_mode)) .add_sized(egui::Vec2::new(84.0, 32.0), sign_out_button())
.clicked() .clicked()
{ {
op = Some(ProfilePreviewOp::SwitchTo); op = Some(ProfilePreviewAction::RemoveAccount)
}
if ui.add(sign_out_button(ui)).clicked() {
op = Some(ProfilePreviewOp::RemoveAccount)
}
} }
}); });
}); });
@@ -183,34 +193,8 @@ fn add_account_button() -> Button<'static> {
.frame(false) .frame(false)
} }
fn sign_out_button(ui: &egui::Ui) -> egui::Button<'static> { fn sign_out_button() -> egui::Button<'static> {
let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png"); egui::Button::new(RichText::new("Sign out"))
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 // PREVIEWS

View File

@@ -1,11 +1,11 @@
use crate::app_style::NotedeckTextStyle; use crate::app_style::{get_font_size, NotedeckTextStyle};
use crate::imgcache::ImageCache; use crate::imgcache::ImageCache;
use crate::storage::{DataPath, DataPathType}; use crate::storage::{DataPath, DataPathType};
use crate::ui::ProfilePic; use crate::ui::ProfilePic;
use crate::user_account::UserAccount; use crate::user_account::UserAccount;
use crate::{colors, images, DisplayName}; use crate::{colors, images, DisplayName};
use egui::load::TexturePoll; use egui::load::TexturePoll;
use egui::{Frame, RichText, Sense, Widget}; use egui::{Frame, Label, RichText, Sense, Widget};
use egui_extras::Size; use egui_extras::Size;
use enostr::NoteId; use enostr::NoteId;
use nostrdb::ProfileRecord; use nostrdb::ProfileRecord;
@@ -93,11 +93,20 @@ impl egui::Widget for ProfilePreview<'_, '_> {
pub struct SimpleProfilePreview<'a, 'cache> { pub struct SimpleProfilePreview<'a, 'cache> {
profile: Option<&'a ProfileRecord<'a>>, profile: Option<&'a ProfileRecord<'a>>,
cache: &'cache mut ImageCache, cache: &'cache mut ImageCache,
is_nsec: bool,
} }
impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> {
pub fn new(profile: Option<&'a ProfileRecord<'a>>, cache: &'cache mut ImageCache) -> Self { pub fn new(
SimpleProfilePreview { profile, cache } 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.add(ProfilePic::new(self.cache, get_profile_url(self.profile)).size(48.0));
ui.vertical(|ui| { ui.vertical(|ui| {
ui.add(display_name_widget(get_display_name(self.profile), true)); 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 .response
@@ -203,8 +222,10 @@ fn display_name_widget(
) -> impl egui::Widget + '_ { ) -> impl egui::Widget + '_ {
move |ui: &mut egui::Ui| match display_name { move |ui: &mut egui::Ui| match display_name {
DisplayName::One(n) => { DisplayName::One(n) => {
let name_response = let name_response = ui.add(
ui.label(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style())); Label::new(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style()))
.selectable(false),
);
if add_placeholder_space { if add_placeholder_space {
ui.add_space(16.0); ui.add_space(16.0);
} }
@@ -215,14 +236,21 @@ fn display_name_widget(
display_name, display_name,
username, username,
} => { } => {
ui.label( ui.add(
RichText::new(display_name).text_style(NotedeckTextStyle::Heading3.text_style()), Label::new(
RichText::new(display_name)
.text_style(NotedeckTextStyle::Heading3.text_style()),
)
.selectable(false),
); );
ui.label( ui.add(
RichText::new(format!("@{}", username)) Label::new(
.size(12.0) RichText::new(format!("@{}", username))
.color(colors::MID_GRAY), .size(12.0)
.color(colors::MID_GRAY),
)
.selectable(false),
) )
} }
} }