notedeck

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

commit 1458498131e384e50d2628de5219c02cffecf66b
parent 577aa76ac79788e164cb825da0fed906efb07005
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 14 Jun 2024 12:16:49 -0700

initial post box view

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
MCargo.lock | 2+-
MCargo.toml | 2+-
Menostr/src/note.rs | 2+-
Msrc/app.rs | 24++++++++++++++----------
Msrc/colors.rs | 4++--
Asrc/draft.rs | 10++++++++++
Msrc/lib.rs | 1+
Msrc/test_data.rs | 11++++++++++-
Msrc/ui/account_management.rs | 4++--
Msrc/ui/account_switcher.rs | 2+-
Msrc/ui/mod.rs | 2+-
Msrc/ui/note/mod.rs | 2++
Asrc/ui/note/post.rs | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/note/reply.rs | 1+
Msrc/ui/profile/picture.rs | 11+++++++++++
Msrc/ui/side_panel.rs | 2+-
Msrc/ui_preview/main.rs | 14++++++++++----
17 files changed, 181 insertions(+), 25 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1108,7 +1108,7 @@ dependencies = [ [[package]] name = "egui_nav" version = "0.1.0" -source = "git+https://github.com/damus-io/egui-nav?rev=ac22dfccbaa3d2fee57a8b0250bde11ebf4c5563#ac22dfccbaa3d2fee57a8b0250bde11ebf4c5563" +source = "git+https://github.com/damus-io/egui-nav?rev=ef7018a261aa0de13b5074ab3e812ef56e44db09#ef7018a261aa0de13b5074ab3e812ef56e44db09" dependencies = [ "egui", "egui_extras", diff --git a/Cargo.toml b/Cargo.toml @@ -32,7 +32,7 @@ eframe = { version = "0.27.2", default-features = false, features = [ "glow", "w egui_extras = { version = "0.27.2", features = ["all_loaders"] } ehttp = "0.2.0" egui_tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "120971fc43db6ba0b6f194f4bd4a66f7e00a4e22" } -egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "ac22dfccbaa3d2fee57a8b0250bde11ebf4c5563" } +egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "ef7018a261aa0de13b5074ab3e812ef56e44db09" } reqwest = { version = "0.12.4", default-features = false, features = [ "rustls-tls-native-roots" ] } image = { version = "0.24", features = ["jpeg", "png", "webp"] } log = "0.4.17" diff --git a/enostr/src/note.rs b/enostr/src/note.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct NoteId([u8; 32]); impl NoteId { diff --git a/src/app.rs b/src/app.rs @@ -1,6 +1,7 @@ use crate::account_manager::AccountManager; use crate::app_creation::setup_cc; use crate::app_style::user_requested_visuals_change; +use crate::draft::Draft; use crate::error::Error; use crate::frame_history::FrameHistory; use crate::imgcache::ImageCache; @@ -15,6 +16,7 @@ use crate::Result; use egui_nav::{Nav, NavAction}; use enostr::RelayPool; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use egui::{Context, Frame, Style}; @@ -46,6 +48,7 @@ pub struct Damus { /// global navigation for account management popups, etc. //nav: Vec<Route>, pub textmode: bool, + pub drafts: HashMap<enostr::NoteId, Draft>, pub timelines: Vec<Timeline>, pub selected_timeline: i32, @@ -706,6 +709,7 @@ impl Damus { Self { is_mobile, + drafts: HashMap::new(), state: DamusState::Initializing, pool: RelayPool::new(), img_cache: ImageCache::new(imgcache_dir), @@ -737,6 +741,7 @@ impl Damus { config.set_ingester_threads(2); Self { is_mobile, + drafts: HashMap::new(), state: DamusState::Initializing, pool: RelayPool::new(), img_cache: ImageCache::new(imgcache_dir), @@ -901,7 +906,8 @@ fn render_nav(routes: Vec<Route>, timeline_ind: usize, app: &mut Damus, ui: &mut } Route::Reply(id) => { - let app = app_ctx.borrow(); + let mut app = app_ctx.borrow_mut(); + let txn = if let Ok(txn) = Transaction::new(&app.ndb) { txn } else { @@ -916,15 +922,13 @@ fn render_nav(routes: Vec<Route>, timeline_ind: usize, app: &mut Damus, ui: &mut return; }; - ui.label(format!( - "Replying to note by {}", - app.ndb - .get_profile_by_pubkey(&txn, note.pubkey()) - .as_ref() - .ok() - .and_then(|pr| Some(crate::profile::get_profile_name(pr)?.username())) - .unwrap_or("??") - )); + let poster = app + .account_manager + .get_selected_account_index() + .unwrap_or(0); + + let replying_to = note.pubkey(); + let _r = ui::PostView::new(&mut app, poster, replying_to).ui(&txn, ui); } }); diff --git a/src/colors.rs b/src/colors.rs @@ -14,7 +14,7 @@ const ORANGE_700: Color32 = Color32::from_rgb(0xF6, 0xB1, 0x4A); const SEMI_DARKER_BG: Color32 = Color32::from_rgb(0x39, 0x39, 0x39); const DARKER_BG: Color32 = Color32::from_rgb(0x1F, 0x1F, 0x1F); const DARK_BG: Color32 = Color32::from_rgb(0x2C, 0x2C, 0x2C); -const DARK_ISH_BG: Color32 = Color32::from_rgb(0x22, 0x22, 0x22); +const DARK_ISH_BG: Color32 = Color32::from_rgb(0x25, 0x25, 0x25); const SEMI_DARK_BG: Color32 = Color32::from_rgb(0x44, 0x44, 0x44); const LIGHTER_GRAY: Color32 = Color32::from_rgb(0xf8, 0xf8, 0xf8); @@ -53,7 +53,7 @@ pub fn desktop_dark_color_theme() -> ColorTheme { ColorTheme { // VISUALS panel_fill: DARKER_BG, - extreme_bg_color: SEMI_DARKER_BG, + extreme_bg_color: DARK_ISH_BG, text_color: Color32::WHITE, err_fg_color: RED_700, warn_fg_color: ORANGE_700, diff --git a/src/draft.rs b/src/draft.rs @@ -0,0 +1,10 @@ +#[derive(Default)] +pub struct Draft { + pub buffer: String, +} + +impl Draft { + pub fn new() -> Self { + Draft::default() + } +} diff --git a/src/lib.rs b/src/lib.rs @@ -8,6 +8,7 @@ pub mod account_manager; pub mod app_creation; mod app_style; mod colors; +mod draft; mod filter; mod fonts; mod frame_history; diff --git a/src/test_data.rs b/src/test_data.rs @@ -55,6 +55,15 @@ const TEST_PROFILE_DATA: [u8; 448] = [ 0x0c, 0x00, 0x24, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x1c, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, ]; +const TEST_PUBKEY: [u8; 32] = [ + 0x32, 0xe1, 0x82, 0x76, 0x35, 0x45, 0x0e, 0xbb, 0x3c, 0x5a, 0x7d, 0x12, 0xc1, 0xf8, 0xe7, 0xb2, + 0xb5, 0x14, 0x43, 0x9a, 0xc1, 0x0a, 0x67, 0xee, 0xf3, 0xd9, 0xfd, 0x9c, 0x5c, 0x68, 0xe2, 0x45, +]; + +pub fn test_pubkey() -> &'static [u8; 32] { + &TEST_PUBKEY +} + pub fn test_profile_record() -> ProfileRecord<'static> { ProfileRecord::new_owned(&TEST_PROFILE_DATA).unwrap() } @@ -83,7 +92,7 @@ pub fn get_test_accounts() -> Vec<UserAccount> { .collect() } -pub fn get_account_manager_test_app(is_mobile: bool) -> Damus { +pub fn test_app(is_mobile: bool) -> Damus { let db_dir = Path::new("."); let path = db_dir.to_str().unwrap(); let mut app = Damus::mock(path, is_mobile); diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs @@ -201,7 +201,7 @@ fn selected_widget() -> impl egui::Widget { mod preview { use super::*; - use crate::test_data::get_account_manager_test_app; + use crate::test_data; pub struct AccountManagementPreview { is_mobile: bool, @@ -210,7 +210,7 @@ mod preview { impl AccountManagementPreview { fn new(is_mobile: bool) -> Self { - let app = get_account_manager_test_app(is_mobile); + let app = test_data::test_app(is_mobile); AccountManagementPreview { is_mobile, app } } diff --git a/src/ui/account_switcher.rs b/src/ui/account_switcher.rs @@ -214,7 +214,7 @@ mod previews { impl AccountSelectionPreview { fn new(is_mobile: bool) -> Self { - let app = test_data::get_account_manager_test_app(is_mobile); + let app = test_data::test_app(is_mobile); AccountSelectionPreview { app } } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs @@ -13,7 +13,7 @@ pub mod username; pub use account_management::AccountManagementView; pub use account_switcher::AccountSelectionWidget; pub use mention::Mention; -pub use note::{BarAction, Note, NoteResponse}; +pub use note::{BarAction, Note, NoteResponse, PostView}; pub use preview::{Preview, PreviewApp, PreviewConfig}; pub use profile::{profile_preview_controller, ProfilePic, ProfilePreview}; pub use relay::RelayView; diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs @@ -1,8 +1,10 @@ pub mod contents; pub mod options; +pub mod post; pub use contents::NoteContents; pub use options::NoteOptions; +pub use post::PostView; use crate::{colors, notecache::CachedNote, ui, ui::View, Damus}; use egui::{Label, RichText, Sense}; diff --git a/src/ui/note/post.rs b/src/ui/note/post.rs @@ -0,0 +1,112 @@ +use crate::app::Damus; +use crate::draft::Draft; +use crate::ui::{Preview, PreviewConfig, View}; +use crate::{ui, Error}; +use egui::widgets::text_edit::TextEdit; +use nostrdb::Transaction; + +pub struct PostView<'app, 'p> { + app: &'app mut Damus, + /// account index + poster: usize, + replying_to: &'p [u8; 32], +} + +impl<'app, 'p> PostView<'app, 'p> { + pub fn new(app: &'app mut Damus, poster: usize, replying_to: &'p [u8; 32]) -> Self { + PostView { + app, + poster, + replying_to, + } + } + + pub fn ui(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> Result<(), Error> { + egui::Frame::default() + .inner_margin(egui::Margin::same(12.0)) + .inner_margin(egui::Margin::same(12.0)) + .fill(ui.visuals().extreme_bg_color) + .stroke(ui.visuals().noninteractive().bg_stroke) + .rounding(12.0) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 12.0; + + let pfp_size = 24.0; + + let poster_pubkey = self + .app + .account_manager + .get_account(self.poster) + .map(|acc| acc.pubkey.bytes()) + .unwrap_or(crate::test_data::test_pubkey()); + + // TODO: refactor pfp control to do all of this for us + let poster_pfp = self + .app + .ndb + .get_profile_by_pubkey(&txn, poster_pubkey) + .as_ref() + .ok() + .and_then(|p| ui::ProfilePic::from_profile(&mut self.app.img_cache, p)); + + if let Some(pfp) = poster_pfp { + ui.add(pfp); + } else { + ui.add( + ui::ProfilePic::new( + &mut self.app.img_cache, + ui::ProfilePic::no_pfp_url(), + ) + .size(pfp_size), + ); + } + + let draft = self + .app + .drafts + .entry(enostr::NoteId::new(*self.replying_to)) + .or_insert_with(|| Draft::new()); + + ui.add(TextEdit::multiline(&mut draft.buffer).frame(false)); + + Ok(()) + }) + .inner + }) + .inner + } +} + +mod preview { + use super::*; + use crate::test_data; + + pub struct PostPreview { + app: Damus, + } + + impl PostPreview { + fn new(is_mobile: bool) -> Self { + PostPreview { + app: test_data::test_app(is_mobile), + } + } + } + + impl View for PostPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + let test_note_id = test_data::test_pubkey(); + let txn = Transaction::new(&self.app.ndb).unwrap(); + let _r = PostView::new(&mut self.app, 0, test_note_id).ui(&txn, ui); + } + } + + impl<'app, 'p> Preview for PostView<'app, 'p> { + type Prev = PostPreview; + + fn preview(cfg: PreviewConfig) -> Self::Prev { + PostPreview::new(cfg.is_mobile) + } + } +} diff --git a/src/ui/note/reply.rs b/src/ui/note/reply.rs @@ -0,0 +1 @@ +struct PostReplyView {} diff --git a/src/ui/profile/picture.rs b/src/ui/profile/picture.rs @@ -20,6 +20,17 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { ProfilePic { cache, url, size } } + pub fn from_profile( + cache: &'cache mut ImageCache, + profile: &nostrdb::ProfileRecord<'url>, + ) -> Option<Self> { + if let Some(url) = profile.record().profile().and_then(|p| p.picture()) { + Some(ProfilePic::new(cache, url)) + } else { + None + } + } + pub fn default_size() -> f32 { 38.0 } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs @@ -122,7 +122,7 @@ mod preview { impl DesktopSidePanelPreview { fn new(is_mobile: bool) -> Self { - let app = test_data::get_account_manager_test_app(is_mobile); + let app = test_data::test_app(is_mobile); DesktopSidePanelPreview { app } } } diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs @@ -3,11 +3,10 @@ use notedeck::app_creation::{ }; use notedeck::ui::account_login_view::AccountLoginView; use notedeck::ui::{ - AccountManagementView, AccountSelectionWidget, DesktopSidePanel, Preview, PreviewApp, + AccountManagementView, AccountSelectionWidget, DesktopSidePanel, PostView, Preview, PreviewApp, PreviewConfig, ProfilePic, ProfilePreview, RelayView, }; use std::env; -use tracing::info; struct PreviewRunner { force_mobile: bool, @@ -16,7 +15,10 @@ struct PreviewRunner { impl PreviewRunner { fn new(force_mobile: bool, light_mode: bool) -> Self { - PreviewRunner { force_mobile, light_mode } + PreviewRunner { + force_mobile, + light_mode, + } } async fn run<P>(self, preview: P) @@ -83,7 +85,10 @@ async fn main() { return; }; - println!("light mode previews: {}", if light_mode { "enabled" } else { "disabled" }); + println!( + "light mode previews: {}", + if light_mode { "enabled" } else { "disabled" } + ); let is_mobile = is_mobile.unwrap_or(notedeck::ui::is_compiled_as_mobile()); let runner = PreviewRunner::new(is_mobile, light_mode); @@ -98,5 +103,6 @@ async fn main() { AccountManagementView, AccountSelectionWidget, DesktopSidePanel, + PostView, ); }