Switch to Columns

Also refactor damus app usage to only pass in things that we need in views.

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2024-09-10 15:27:54 -07:00
parent 4379466d1d
commit 00091c5088
29 changed files with 1256 additions and 759 deletions

View File

@@ -40,7 +40,7 @@ impl AccountManagementView {
let maybe_remove =
profile_preview_controller::set_profile_previews(app, ui, account_card_ui());
Self::maybe_remove_accounts(&mut app.account_manager, maybe_remove);
Self::maybe_remove_accounts(&mut app.accounts, maybe_remove);
}
fn show_accounts_mobile(app: &mut Damus, ui: &mut egui::Ui) {
@@ -56,7 +56,7 @@ impl AccountManagementView {
);
// remove all account indicies user requested
Self::maybe_remove_accounts(&mut app.account_manager, maybe_remove);
Self::maybe_remove_accounts(&mut app.accounts, maybe_remove);
},
);
}
@@ -95,8 +95,8 @@ impl AccountManagementView {
// Layout::right_to_left(egui::Align::Center),
// |ui| {
// if ui.add(logout_all_button()).clicked() {
// for index in (0..self.account_manager.num_accounts()).rev() {
// self.account_manager.remove_account(index);
// for index in (0..self.accounts.num_accounts()).rev() {
// self.accounts.remove_account(index);
// }
// }
// },

View File

@@ -49,12 +49,10 @@ impl AccountSelectionWidget {
fn perform_action(app: &mut Damus, action: AccountSelectAction) {
match action {
AccountSelectAction::RemoveAccount { _index } => {
app.account_manager.remove_account(_index)
}
AccountSelectAction::RemoveAccount { _index } => app.accounts.remove_account(_index),
AccountSelectAction::SelectAccount { _index } => {
app.show_account_switcher = false;
app.account_manager.select_account(_index);
app.accounts.select_account(_index);
}
AccountSelectAction::OpenAccountManagement => {
app.show_account_switcher = false;
@@ -66,7 +64,7 @@ impl AccountSelectionWidget {
fn show(app: &mut Damus, ui: &mut egui::Ui) -> (AccountSelectResponse, egui::Response) {
let mut res = AccountSelectResponse::default();
let mut selected_index = app.account_manager.get_selected_account_index();
let mut selected_index = app.accounts.get_selected_account_index();
let response = Frame::none()
.outer_margin(8.0)
@@ -83,7 +81,7 @@ impl AccountSelectionWidget {
ui.add(add_account_button());
if let Some(_index) = selected_index {
if let Some(account) = app.account_manager.get_account(_index) {
if let Some(account) = app.accounts.get_account(_index) {
ui.add_space(8.0);
if Self::handle_sign_out(&app.ndb, ui, account) {
res.action = Some(AccountSelectAction::RemoveAccount { _index })

View File

@@ -1,8 +1,9 @@
use crate::{colors, ui, Damus};
use nostrdb::Transaction;
use crate::{colors, imgcache::ImageCache, ui};
use nostrdb::{Ndb, Transaction};
pub struct Mention<'a> {
app: &'a mut Damus,
ndb: &'a Ndb,
img_cache: &'a mut ImageCache,
txn: &'a Transaction,
pk: &'a [u8; 32],
selectable: bool,
@@ -10,11 +11,17 @@ pub struct Mention<'a> {
}
impl<'a> Mention<'a> {
pub fn new(app: &'a mut Damus, txn: &'a Transaction, pk: &'a [u8; 32]) -> Self {
pub fn new(
ndb: &'a Ndb,
img_cache: &'a mut ImageCache,
txn: &'a Transaction,
pk: &'a [u8; 32],
) -> Self {
let size = 16.0;
let selectable = true;
Mention {
app,
ndb,
img_cache,
txn,
pk,
selectable,
@@ -35,12 +42,21 @@ impl<'a> Mention<'a> {
impl<'a> egui::Widget for Mention<'a> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
mention_ui(self.app, self.txn, self.pk, ui, self.size, self.selectable)
mention_ui(
self.ndb,
self.img_cache,
self.txn,
self.pk,
ui,
self.size,
self.selectable,
)
}
}
fn mention_ui(
app: &mut Damus,
ndb: &Ndb,
img_cache: &mut ImageCache,
txn: &Transaction,
pk: &[u8; 32],
ui: &mut egui::Ui,
@@ -51,7 +67,7 @@ fn mention_ui(
puffin::profile_function!();
ui.horizontal(|ui| {
let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok();
let profile = ndb.get_profile_by_pubkey(txn, pk).ok();
let name: String =
if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) {
@@ -68,7 +84,7 @@ fn mention_ui(
if let Some(rec) = profile.as_ref() {
resp.on_hover_ui_at_pointer(|ui| {
ui.set_max_width(300.0);
ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache));
ui.add(ui::ProfilePreview::new(rec, img_cache));
});
}
})

View File

@@ -31,7 +31,9 @@ pub use username::Username;
use egui::Margin;
/// This is kind of like the Widget trait but is meant for larger top-level
/// views that are typically stateful. The Widget trait forces us to add mutable
/// views that are typically stateful.
///
/// The Widget trait forces us to add mutable
/// implementations at the type level, which screws us when generating Previews
/// for a Widget. I would have just Widget instead of making this Trait otherwise.
///

View File

@@ -1,14 +1,17 @@
use crate::images::ImageType;
use crate::imgcache::ImageCache;
use crate::notecache::NoteCache;
use crate::ui::note::NoteOptions;
use crate::ui::ProfilePic;
use crate::{colors, ui, Damus};
use crate::{colors, ui};
use egui::{Color32, Hyperlink, Image, RichText};
use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction};
use nostrdb::{BlockType, Mention, Ndb, Note, NoteKey, Transaction};
use tracing::warn;
pub struct NoteContents<'a> {
damus: &'a mut Damus,
ndb: &'a Ndb,
img_cache: &'a mut ImageCache,
note_cache: &'a mut NoteCache,
txn: &'a Transaction,
note: &'a Note<'a>,
note_key: NoteKey,
@@ -17,14 +20,18 @@ pub struct NoteContents<'a> {
impl<'a> NoteContents<'a> {
pub fn new(
damus: &'a mut Damus,
ndb: &'a Ndb,
img_cache: &'a mut ImageCache,
note_cache: &'a mut NoteCache,
txn: &'a Transaction,
note: &'a Note,
note_key: NoteKey,
options: ui::note::NoteOptions,
) -> Self {
NoteContents {
damus,
ndb,
img_cache,
note_cache,
txn,
note,
note_key,
@@ -37,7 +44,9 @@ impl egui::Widget for NoteContents<'_> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
render_note_contents(
ui,
self.damus,
self.ndb,
self.img_cache,
self.note_cache,
self.txn,
self.note,
self.note_key,
@@ -51,7 +60,9 @@ impl egui::Widget for NoteContents<'_> {
/// notes are references within a note
fn render_note_preview(
ui: &mut egui::Ui,
app: &mut Damus,
ndb: &Ndb,
note_cache: &mut NoteCache,
img_cache: &mut ImageCache,
txn: &Transaction,
id: &[u8; 32],
_id_str: &str,
@@ -59,7 +70,7 @@ fn render_note_preview(
#[cfg(feature = "profiling")]
puffin::profile_function!();
let note = if let Ok(note) = app.ndb.get_note_by_id(txn, id) {
let note = if let Ok(note) = ndb.get_note_by_id(txn, id) {
// TODO: support other preview kinds
if note.kind() == 1 {
note
@@ -92,7 +103,7 @@ fn render_note_preview(
ui.visuals().noninteractive().bg_stroke.color,
))
.show(ui, |ui| {
ui::NoteView::new(app, &note)
ui::NoteView::new(ndb, note_cache, img_cache, &note)
.actionbar(false)
.small_pfp(true)
.wide(true)
@@ -102,9 +113,12 @@ fn render_note_preview(
.response
}
#[allow(clippy::too_many_arguments)]
fn render_note_contents(
ui: &mut egui::Ui,
damus: &mut Damus,
ndb: &Ndb,
img_cache: &mut ImageCache,
note_cache: &mut NoteCache,
txn: &Transaction,
note: &Note,
note_key: NoteKey,
@@ -118,7 +132,7 @@ fn render_note_contents(
let mut inline_note: Option<(&[u8; 32], &str)> = None;
let resp = ui.horizontal_wrapped(|ui| {
let blocks = if let Ok(blocks) = damus.ndb.get_blocks_by_key(txn, note_key) {
let blocks = if let Ok(blocks) = ndb.get_blocks_by_key(txn, note_key) {
blocks
} else {
warn!("missing note content blocks? '{}'", note.content());
@@ -132,11 +146,11 @@ fn render_note_contents(
match block.blocktype() {
BlockType::MentionBech32 => match block.as_mention().unwrap() {
Mention::Profile(profile) => {
ui.add(ui::Mention::new(damus, txn, profile.pubkey()));
ui.add(ui::Mention::new(ndb, img_cache, txn, profile.pubkey()));
}
Mention::Pubkey(npub) => {
ui.add(ui::Mention::new(damus, txn, npub.pubkey()));
ui.add(ui::Mention::new(ndb, img_cache, txn, npub.pubkey()));
}
Mention::Note(note) if options.has_note_previews() => {
@@ -186,13 +200,13 @@ fn render_note_contents(
});
if let Some((id, block_str)) = inline_note {
render_note_preview(ui, damus, txn, id, block_str);
render_note_preview(ui, ndb, note_cache, img_cache, txn, id, block_str);
}
if !images.is_empty() && !damus.textmode {
if !images.is_empty() && !options.has_textmode() {
ui.add_space(2.0);
let carousel_id = egui::Id::new(("carousel", note.key().expect("expected tx note")));
image_carousel(ui, &mut damus.img_cache, images, carousel_id);
image_carousel(ui, img_cache, images, carousel_id);
ui.add_space(2.0);
}

View File

@@ -8,12 +8,21 @@ pub use options::NoteOptions;
pub use post::{PostAction, PostResponse, PostView};
pub use reply::PostReplyView;
use crate::{actionbar::BarAction, colors, notecache::CachedNote, ui, ui::View, Damus};
use crate::{
actionbar::BarAction,
colors,
imgcache::ImageCache,
notecache::{CachedNote, NoteCache},
ui,
ui::View,
};
use egui::{Label, RichText, Sense};
use nostrdb::{Note, NoteKey, NoteReply, Transaction};
use nostrdb::{Ndb, Note, NoteKey, NoteReply, Transaction};
pub struct NoteView<'a> {
app: &'a mut Damus,
ndb: &'a Ndb,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
note: &'a nostrdb::Note<'a>,
flags: NoteOptions,
}
@@ -29,7 +38,13 @@ impl<'a> View for NoteView<'a> {
}
}
fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app: &mut Damus) {
fn reply_desc(
ui: &mut egui::Ui,
txn: &Transaction,
note_reply: &NoteReply,
ndb: &Ndb,
img_cache: &mut ImageCache,
) {
#[cfg(feature = "profiling")]
puffin::profile_function!();
@@ -51,7 +66,7 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
return;
};
let reply_note = if let Ok(reply_note) = app.ndb.get_note_by_id(txn, reply.id) {
let reply_note = if let Ok(reply_note) = ndb.get_note_by_id(txn, reply.id) {
reply_note
} else {
ui.add(
@@ -68,7 +83,7 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
if note_reply.is_reply_to_root() {
// We're replying to the root, let's show this
ui.add(
ui::Mention::new(app, txn, reply_note.pubkey())
ui::Mention::new(ndb, img_cache, txn, reply_note.pubkey())
.size(size)
.selectable(selectable),
);
@@ -83,11 +98,11 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
} else if let Some(root) = note_reply.root() {
// replying to another post in a thread, not the root
if let Ok(root_note) = app.ndb.get_note_by_id(txn, root.id) {
if let Ok(root_note) = ndb.get_note_by_id(txn, root.id) {
if root_note.pubkey() == reply_note.pubkey() {
// simply "replying to bob's note" when replying to bob in his thread
ui.add(
ui::Mention::new(app, txn, reply_note.pubkey())
ui::Mention::new(ndb, img_cache, txn, reply_note.pubkey())
.size(size)
.selectable(selectable),
);
@@ -103,7 +118,7 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
// replying to bob in alice's thread
ui.add(
ui::Mention::new(app, txn, reply_note.pubkey())
ui::Mention::new(ndb, img_cache, txn, reply_note.pubkey())
.size(size)
.selectable(selectable),
);
@@ -112,7 +127,7 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
.selectable(selectable),
);
ui.add(
ui::Mention::new(app, txn, root_note.pubkey())
ui::Mention::new(ndb, img_cache, txn, root_note.pubkey())
.size(size)
.selectable(selectable),
);
@@ -127,7 +142,7 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
}
} else {
ui.add(
ui::Mention::new(app, txn, reply_note.pubkey())
ui::Mention::new(ndb, img_cache, txn, reply_note.pubkey())
.size(size)
.selectable(selectable),
);
@@ -144,9 +159,25 @@ fn reply_desc(ui: &mut egui::Ui, txn: &Transaction, note_reply: &NoteReply, app:
}
impl<'a> NoteView<'a> {
pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self {
pub fn new(
ndb: &'a Ndb,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
note: &'a nostrdb::Note<'a>,
) -> Self {
let flags = NoteOptions::actionbar | NoteOptions::note_previews;
Self { app, note, flags }
Self {
ndb,
note_cache,
img_cache,
note,
flags,
}
}
pub fn textmode(mut self, enable: bool) -> Self {
self.options_mut().set_textmode(enable);
self
}
pub fn actionbar(mut self, enable: bool) -> Self {
@@ -192,14 +223,13 @@ impl<'a> NoteView<'a> {
let txn = self.note.txn().expect("todo: implement non-db notes");
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
//ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 2.0;
let cached_note = self
.app
.note_cache_mut()
.note_cache
.cached_note_or_insert_mut(note_key, self.note);
let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0));
@@ -218,7 +248,13 @@ impl<'a> NoteView<'a> {
});
ui.add(NoteContents::new(
self.app, txn, self.note, note_key, self.flags,
self.ndb,
self.img_cache,
self.note_cache,
txn,
self.note,
note_key,
self.flags,
));
//});
})
@@ -255,33 +291,26 @@ impl<'a> NoteView<'a> {
let profile_key = profile.as_ref().unwrap().record().note_key();
let note_key = note_key.as_u64();
if ui::is_narrow(ui.ctx()) {
ui.add(ui::ProfilePic::new(&mut self.app.img_cache, pic));
} else {
let (rect, size, _resp) = ui::anim::hover_expand(
ui,
egui::Id::new((profile_key, note_key)),
pfp_size,
ui::NoteView::expand_size(),
anim_speed,
);
let (rect, size, _resp) = ui::anim::hover_expand(
ui,
egui::Id::new((profile_key, note_key)),
pfp_size,
ui::NoteView::expand_size(),
anim_speed,
);
ui.put(
rect,
ui::ProfilePic::new(&mut self.app.img_cache, pic).size(size),
)
ui.put(rect, ui::ProfilePic::new(self.img_cache, pic).size(size))
.on_hover_ui_at_pointer(|ui| {
ui.set_max_width(300.0);
ui.add(ui::ProfilePreview::new(
profile.as_ref().unwrap(),
&mut self.app.img_cache,
self.img_cache,
));
});
}
}
None => {
ui.add(
ui::ProfilePic::new(&mut self.app.img_cache, ui::ProfilePic::no_pfp_url())
ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url())
.size(pfp_size),
);
}
@@ -289,7 +318,7 @@ impl<'a> NoteView<'a> {
}
pub fn show(&mut self, ui: &mut egui::Ui) -> NoteResponse {
if self.app.textmode {
if self.options().has_textmode() {
NoteResponse {
response: self.textmode_ui(ui),
action: None,
@@ -301,7 +330,7 @@ impl<'a> NoteView<'a> {
fn note_header(
ui: &mut egui::Ui,
app: &mut Damus,
note_cache: &mut NoteCache,
note: &Note,
profile: &Result<nostrdb::ProfileRecord<'_>, nostrdb::Error>,
) -> egui::Response {
@@ -311,9 +340,7 @@ impl<'a> NoteView<'a> {
ui.spacing_mut().item_spacing.x = 2.0;
ui.add(ui::Username::new(profile.as_ref().ok(), note.pubkey()).abbreviated(20));
let cached_note = app
.note_cache_mut()
.cached_note_or_insert_mut(note_key, note);
let cached_note = note_cache.cached_note_or_insert_mut(note_key, note);
render_reltime(ui, cached_note, true);
})
.response
@@ -325,7 +352,7 @@ impl<'a> NoteView<'a> {
let note_key = self.note.key().expect("todo: support non-db notes");
let txn = self.note.txn().expect("todo: support non-db notes");
let mut note_action: Option<BarAction> = None;
let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
// wide design
let response = if self.options().has_wide() {
@@ -336,28 +363,29 @@ impl<'a> NoteView<'a> {
ui.vertical(|ui| {
ui.add_sized([size.x, self.options().pfp_size()], |ui: &mut egui::Ui| {
ui.horizontal_centered(|ui| {
NoteView::note_header(ui, self.app, self.note, &profile);
NoteView::note_header(ui, self.note_cache, self.note, &profile);
})
.response
});
let note_reply = self
.app
.note_cache_mut()
.note_cache
.cached_note_or_insert_mut(note_key, self.note)
.reply
.borrow(self.note.tags());
if note_reply.reply().is_some() {
ui.horizontal(|ui| {
reply_desc(ui, txn, &note_reply, self.app);
reply_desc(ui, txn, &note_reply, self.ndb, self.img_cache);
});
}
});
});
let resp = ui.add(NoteContents::new(
self.app,
self.ndb,
self.img_cache,
self.note_cache,
txn,
self.note,
note_key,
@@ -375,25 +403,26 @@ impl<'a> NoteView<'a> {
self.pfp(note_key, &profile, ui);
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
NoteView::note_header(ui, self.app, self.note, &profile);
NoteView::note_header(ui, self.note_cache, self.note, &profile);
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 2.0;
let note_reply = self
.app
.note_cache_mut()
.note_cache
.cached_note_or_insert_mut(note_key, self.note)
.reply
.borrow(self.note.tags());
if note_reply.reply().is_some() {
reply_desc(ui, txn, &note_reply, self.app);
reply_desc(ui, txn, &note_reply, self.ndb, self.img_cache);
}
});
ui.add(NoteContents::new(
self.app,
self.ndb,
self.img_cache,
self.note_cache,
txn,
self.note,
note_key,

View File

@@ -12,6 +12,7 @@ bitflags! {
const medium_pfp = 0b00001000;
const wide = 0b00010000;
const selectable_text = 0b00100000;
const textmode = 0b01000000;
}
}
@@ -33,6 +34,7 @@ impl NoteOptions {
create_setter!(set_medium_pfp, medium_pfp);
create_setter!(set_note_previews, note_previews);
create_setter!(set_selectable_text, selectable_text);
create_setter!(set_textmode, textmode);
create_setter!(set_actionbar, actionbar);
#[inline]
@@ -45,6 +47,11 @@ impl NoteOptions {
(self & NoteOptions::selectable_text) == NoteOptions::selectable_text
}
#[inline]
pub fn has_textmode(self) -> bool {
(self & NoteOptions::textmode) == NoteOptions::textmode
}
#[inline]
pub fn has_note_previews(self) -> bool {
(self & NoteOptions::note_previews) == NoteOptions::note_previews

View File

@@ -1,16 +1,17 @@
use crate::app::Damus;
use crate::draft::{Draft, DraftSource};
use crate::draft::Draft;
use crate::imgcache::ImageCache;
use crate::post::NewPost;
use crate::ui;
use crate::ui::{Preview, PreviewConfig, View};
use egui::widgets::text_edit::TextEdit;
use nostrdb::Transaction;
use enostr::{FilledKeypair, FullKeypair};
use nostrdb::{Config, Ndb, Transaction};
pub struct PostView<'app, 'd> {
app: &'app mut Damus,
/// account index
poster: usize,
draft_source: DraftSource<'d>,
pub struct PostView<'a> {
ndb: &'a Ndb,
draft: &'a mut Draft,
img_cache: &'a mut ImageCache,
poster: FilledKeypair<'a>,
id_source: Option<egui::Id>,
}
@@ -23,14 +24,20 @@ pub struct PostResponse {
pub edit_response: egui::Response,
}
impl<'app, 'd> PostView<'app, 'd> {
pub fn new(app: &'app mut Damus, draft_source: DraftSource<'d>, poster: usize) -> Self {
impl<'a> PostView<'a> {
pub fn new(
ndb: &'a Ndb,
draft: &'a mut Draft,
img_cache: &'a mut ImageCache,
poster: FilledKeypair<'a>,
) -> Self {
let id_source: Option<egui::Id> = None;
PostView {
id_source,
app,
ndb,
draft,
img_cache,
poster,
draft_source,
id_source,
}
}
@@ -39,47 +46,30 @@ impl<'app, 'd> PostView<'app, 'd> {
self
}
fn draft(&mut self) -> &mut Draft {
self.draft_source.draft(&mut self.app.drafts)
}
fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response {
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)
.get_profile_by_pubkey(txn, self.poster.pubkey.bytes())
.as_ref()
.ok()
.and_then(|p| {
Some(ui::ProfilePic::from_profile(&mut self.app.img_cache, p)?.size(pfp_size))
});
.and_then(|p| Some(ui::ProfilePic::from_profile(self.img_cache, p)?.size(pfp_size)));
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),
ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url()).size(pfp_size),
);
}
let buffer = &mut self.draft_source.draft(&mut self.app.drafts).buffer;
let response = ui.add_sized(
ui.available_size(),
TextEdit::multiline(buffer)
TextEdit::multiline(&mut self.draft.buffer)
.hint_text(egui::RichText::new("Write a banger note here...").weak())
.frame(false),
);
@@ -144,10 +134,10 @@ impl<'app, 'd> PostView<'app, 'd> {
.add_sized([91.0, 32.0], egui::Button::new("Post now"))
.clicked()
{
Some(PostAction::Post(NewPost {
content: self.draft().buffer.clone(),
account: self.poster,
}))
Some(PostAction::Post(NewPost::new(
self.draft.buffer.clone(),
self.poster.to_full(),
)))
} else {
None
}
@@ -167,28 +157,41 @@ impl<'app, 'd> PostView<'app, 'd> {
mod preview {
use super::*;
use crate::test_data;
pub struct PostPreview {
app: Damus,
ndb: Ndb,
img_cache: ImageCache,
draft: Draft,
poster: FullKeypair,
}
impl PostPreview {
fn new() -> Self {
let ndb = Ndb::new(".", &Config::new()).expect("ndb");
PostPreview {
app: test_data::test_app(),
ndb,
img_cache: ImageCache::new(".".into()),
draft: Draft::new(),
poster: FullKeypair::generate(),
}
}
}
impl View for PostPreview {
fn ui(&mut self, ui: &mut egui::Ui) {
let txn = Transaction::new(&self.app.ndb).unwrap();
PostView::new(&mut self.app, DraftSource::Compose, 0).ui(&txn, ui);
let txn = Transaction::new(&self.ndb).expect("txn");
PostView::new(
&self.ndb,
&mut self.draft,
&mut self.img_cache,
self.poster.to_filled(),
)
.ui(&txn, ui);
}
}
impl<'app, 'p> Preview for PostView<'app, 'p> {
impl<'a> Preview for PostView<'a> {
type Prev = PostPreview;
fn preview(_cfg: PreviewConfig) -> Self::Prev {

View File

@@ -1,21 +1,43 @@
use crate::draft::DraftSource;
use crate::draft::Drafts;
use crate::imgcache::ImageCache;
use crate::notecache::NoteCache;
use crate::ui;
use crate::ui::note::{PostAction, PostResponse};
use crate::{ui, Damus};
use enostr::{FilledKeypair, RelayPool};
use nostrdb::Ndb;
use tracing::info;
pub struct PostReplyView<'a> {
app: &'a mut Damus,
id_source: Option<egui::Id>,
ndb: &'a Ndb,
poster: FilledKeypair<'a>,
pool: &'a mut RelayPool,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
drafts: &'a mut Drafts,
note: &'a nostrdb::Note<'a>,
id_source: Option<egui::Id>,
}
impl<'a> PostReplyView<'a> {
pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self {
pub fn new(
ndb: &'a Ndb,
poster: FilledKeypair<'a>,
pool: &'a mut RelayPool,
drafts: &'a mut Drafts,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
note: &'a nostrdb::Note<'a>,
) -> Self {
let id_source: Option<egui::Id> = None;
PostReplyView {
app,
id_source,
ndb,
poster,
pool,
drafts,
note,
note_cache,
img_cache,
id_source,
}
}
@@ -46,7 +68,7 @@ impl<'a> PostReplyView<'a> {
egui::Frame::none()
.outer_margin(egui::Margin::same(note_offset))
.show(ui, |ui| {
ui::NoteView::new(self.app, self.note)
ui::NoteView::new(self.ndb, self.note_cache, self.img_cache, self.note)
.actionbar(false)
.medium_pfp(true)
.show(ui);
@@ -54,43 +76,26 @@ impl<'a> PostReplyView<'a> {
let id = self.id();
let replying_to = self.note.id();
let draft_source = DraftSource::Reply(replying_to);
let poster = self
.app
.account_manager
.get_selected_account_index()
.unwrap_or(0);
let rect_before_post = ui.min_rect();
let post_response = ui::PostView::new(self.app, draft_source, poster)
.id_source(id)
.ui(self.note.txn().unwrap(), ui);
if self
.app
.account_manager
.get_selected_account()
.map_or(false, |a| a.secret_key.is_some())
{
if let Some(action) = &post_response.action {
match action {
PostAction::Post(np) => {
let seckey = self
.app
.account_manager
.get_account(poster)
.unwrap()
.secret_key
.as_ref()
.unwrap()
.to_secret_bytes();
let post_response = {
let draft = self.drafts.reply_mut(replying_to);
ui::PostView::new(self.ndb, draft, self.img_cache, self.poster)
.id_source(id)
.ui(self.note.txn().unwrap(), ui)
};
let note = np.to_reply(&seckey, self.note);
if let Some(action) = &post_response.action {
match action {
PostAction::Post(np) => {
let seckey = self.poster.secret_key.to_secret_bytes();
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
self.app.pool.send(&enostr::ClientMessage::raw(raw_msg));
self.app.drafts.clear(DraftSource::Reply(replying_to));
}
let note = np.to_reply(&seckey, self.note);
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
self.pool.send(&enostr::ClientMessage::raw(raw_msg));
self.drafts.reply_mut(replying_to).clear();
}
}
}

View File

@@ -33,8 +33,8 @@ pub fn set_profile_previews(
return None;
};
for i in 0..app.account_manager.num_accounts() {
let account = if let Some(account) = app.account_manager.get_account(i) {
for i in 0..app.accounts.num_accounts() {
let account = if let Some(account) = app.accounts.get_account(i) {
account
} else {
continue;
@@ -47,7 +47,7 @@ pub fn set_profile_previews(
let preview = SimpleProfilePreview::new(profile.as_ref(), &mut app.img_cache);
let is_selected = if let Some(selected) = app.account_manager.get_selected_account_index() {
let is_selected = if let Some(selected) = app.accounts.get_selected_account_index() {
i == selected
} else {
false
@@ -66,7 +66,7 @@ pub fn set_profile_previews(
}
to_remove.as_mut().unwrap().push(i);
}
ProfilePreviewOp::SwitchTo => app.account_manager.select_account(i),
ProfilePreviewOp::SwitchTo => app.accounts.select_account(i),
}
}
@@ -92,8 +92,8 @@ pub fn view_profile_previews(
return None;
};
for i in 0..app.account_manager.num_accounts() {
let account = if let Some(account) = app.account_manager.get_account(i) {
for i in 0..app.accounts.num_accounts() {
let account = if let Some(account) = app.accounts.get_account(i) {
account
} else {
continue;
@@ -106,7 +106,7 @@ pub fn view_profile_previews(
let preview = SimpleProfilePreview::new(profile.as_ref(), &mut app.img_cache);
let is_selected = if let Some(selected) = app.account_manager.get_selected_account_index() {
let is_selected = if let Some(selected) = app.accounts.get_selected_account_index() {
i == selected
} else {
false
@@ -136,7 +136,7 @@ pub fn show_with_selected_pfp(
ui: &mut egui::Ui,
ui_element: fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response,
) -> Option<egui::Response> {
let selected_account = app.account_manager.get_selected_account();
let selected_account = app.accounts.get_selected_account();
if let Some(selected_account) = selected_account {
if let Ok(txn) = Transaction::new(&app.ndb) {
let profile = app

View File

@@ -1,28 +1,57 @@
use crate::{actionbar::BarResult, timeline::TimelineSource, ui, Damus};
use nostrdb::{NoteKey, Transaction};
use crate::{
actionbar::BarResult, column::Columns, imgcache::ImageCache, notecache::NoteCache,
thread::Threads, timeline::TimelineSource, ui, unknowns::UnknownIds,
};
use enostr::RelayPool;
use nostrdb::{Ndb, NoteKey, Transaction};
use tracing::{error, warn};
pub struct ThreadView<'a> {
app: &'a mut Damus,
timeline: usize,
column: usize,
columns: &'a mut Columns,
threads: &'a mut Threads,
ndb: &'a Ndb,
pool: &'a mut RelayPool,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
unknown_ids: &'a mut UnknownIds,
selected_note_id: &'a [u8; 32],
textmode: bool,
}
impl<'a> ThreadView<'a> {
pub fn new(app: &'a mut Damus, timeline: usize, selected_note_id: &'a [u8; 32]) -> Self {
#[allow(clippy::too_many_arguments)]
pub fn new(
column: usize,
columns: &'a mut Columns,
threads: &'a mut Threads,
ndb: &'a Ndb,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
unknown_ids: &'a mut UnknownIds,
pool: &'a mut RelayPool,
textmode: bool,
selected_note_id: &'a [u8; 32],
) -> Self {
ThreadView {
app,
timeline,
column,
columns,
threads,
ndb,
note_cache,
img_cache,
textmode,
selected_note_id,
unknown_ids,
pool,
}
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<BarResult> {
let txn = Transaction::new(&self.app.ndb).expect("txn");
let txn = Transaction::new(self.ndb).expect("txn");
let mut result: Option<BarResult> = None;
let selected_note_key = if let Ok(key) = self
.app
.ndb
.get_notekey_by_id(&txn, self.selected_note_id)
.map(NoteKey::new)
@@ -33,12 +62,13 @@ impl<'a> ThreadView<'a> {
return None;
};
let scroll_id = egui::Id::new((
"threadscroll",
self.app.timelines[self.timeline].selected_view,
self.timeline,
selected_note_key,
));
let scroll_id = {
egui::Id::new((
"threadscroll",
self.columns.column(self.column).view_id(),
selected_note_key,
))
};
ui.label(
egui::RichText::new("Threads ALPHA! It's not done. Things will be broken.")
@@ -51,7 +81,7 @@ impl<'a> ThreadView<'a> {
.auto_shrink([false, false])
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible)
.show(ui, |ui| {
let note = if let Ok(note) = self.app.ndb.get_note_by_key(&txn, selected_note_key) {
let note = if let Ok(note) = self.ndb.get_note_by_key(&txn, selected_note_key) {
note
} else {
return;
@@ -59,8 +89,7 @@ impl<'a> ThreadView<'a> {
let root_id = {
let cached_note = self
.app
.note_cache_mut()
.note_cache
.cached_note_or_insert(selected_note_key, &note);
cached_note
@@ -71,17 +100,19 @@ impl<'a> ThreadView<'a> {
};
// poll for new notes and insert them into our existing notes
if let Err(e) = TimelineSource::Thread(root_id).poll_notes_into_view(&txn, self.app)
{
if let Err(e) = TimelineSource::Thread(root_id).poll_notes_into_view(
&txn,
self.ndb,
self.columns,
self.threads,
self.unknown_ids,
self.note_cache,
) {
error!("Thread::poll_notes_into_view: {e}");
}
let (len, list) = {
let thread = self
.app
.threads
.thread_mut(&self.app.ndb, &txn, root_id)
.get_ptr();
let thread = self.threads.thread_mut(self.ndb, &txn, root_id).get_ptr();
let len = thread.view.notes.len();
(len, &mut thread.view.list)
@@ -95,15 +126,11 @@ impl<'a> ThreadView<'a> {
let ind = len - 1 - start_index;
let note_key = {
let thread = self
.app
.threads
.thread_mut(&self.app.ndb, &txn, root_id)
.get_ptr();
let thread = self.threads.thread_mut(self.ndb, &txn, root_id).get_ptr();
thread.view.notes[ind].key
};
let note = if let Ok(note) = self.app.ndb.get_note_by_key(&txn, note_key) {
let note = if let Ok(note) = self.ndb.get_note_by_key(&txn, note_key) {
note
} else {
warn!("failed to query note {:?}", note_key);
@@ -111,13 +138,22 @@ impl<'a> ThreadView<'a> {
};
ui::padding(8.0, ui, |ui| {
let textmode = self.app.textmode;
let resp = ui::NoteView::new(self.app, &note)
.note_previews(!textmode)
.show(ui);
let resp =
ui::NoteView::new(self.ndb, self.note_cache, self.img_cache, &note)
.note_previews(!self.textmode)
.textmode(self.textmode)
.show(ui);
if let Some(action) = resp.action {
let br = action.execute(self.app, self.timeline, note.id(), &txn);
let br = action.execute(
self.ndb,
self.columns.column_mut(self.column),
self.threads,
self.note_cache,
self.pool,
note.id(),
&txn,
);
if br.is_some() {
result = br;
}

View File

@@ -1,28 +1,67 @@
use crate::{actionbar::BarResult, draft::DraftSource, ui, ui::note::PostAction, Damus};
use crate::{
actionbar::BarAction,
actionbar::BarResult,
column::{Column, ColumnKind},
draft::Drafts,
imgcache::ImageCache,
notecache::NoteCache,
thread::Threads,
ui,
ui::note::PostAction,
};
use egui::containers::scroll_area::ScrollBarVisibility;
use egui::{Direction, Layout};
use egui_tabs::TabColor;
use nostrdb::Transaction;
use enostr::{FilledKeypair, RelayPool};
use nostrdb::{Ndb, Note, Transaction};
use tracing::{debug, info, warn};
pub struct TimelineView<'a> {
app: &'a mut Damus,
ndb: &'a Ndb,
column: &'a mut Column,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
threads: &'a mut Threads,
pool: &'a mut RelayPool,
textmode: bool,
reverse: bool,
timeline: usize,
}
impl<'a> TimelineView<'a> {
pub fn new(app: &'a mut Damus, timeline: usize) -> TimelineView<'a> {
pub fn new(
ndb: &'a Ndb,
column: &'a mut Column,
note_cache: &'a mut NoteCache,
img_cache: &'a mut ImageCache,
threads: &'a mut Threads,
pool: &'a mut RelayPool,
textmode: bool,
) -> TimelineView<'a> {
let reverse = false;
TimelineView {
app,
timeline,
ndb,
column,
note_cache,
img_cache,
threads,
pool,
reverse,
textmode,
}
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
timeline_ui(ui, self.app, self.timeline, self.reverse);
timeline_ui(
ui,
self.ndb,
self.column,
self.note_cache,
self.img_cache,
self.threads,
self.pool,
self.reverse,
self.textmode,
);
}
pub fn reversed(mut self) -> Self {
@@ -31,39 +70,61 @@ impl<'a> TimelineView<'a> {
}
}
fn timeline_ui(ui: &mut egui::Ui, app: &mut Damus, timeline: usize, reversed: bool) {
#[allow(clippy::too_many_arguments)]
fn timeline_ui(
ui: &mut egui::Ui,
ndb: &Ndb,
column: &mut Column,
note_cache: &mut NoteCache,
img_cache: &mut ImageCache,
threads: &mut Threads,
pool: &mut RelayPool,
reversed: bool,
textmode: bool,
) {
//padding(4.0, ui, |ui| ui.heading("Notifications"));
/*
let font_id = egui::TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y;
*/
if timeline == 0 {
postbox_view(app, ui);
{
let timeline = if let ColumnKind::Timeline(timeline) = column.kind_mut() {
timeline
} else {
return;
};
timeline.selected_view = tabs_ui(ui);
// need this for some reason??
ui.add_space(3.0);
}
app.timelines[timeline].selected_view = tabs_ui(ui);
// need this for some reason??
ui.add_space(3.0);
let scroll_id = egui::Id::new(("tlscroll", app.timelines[timeline].selected_view, timeline));
let scroll_id = egui::Id::new(("tlscroll", column.view_id()));
egui::ScrollArea::vertical()
.id_source(scroll_id)
.animated(false)
.auto_shrink([false, false])
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.show(ui, |ui| {
let view = app.timelines[timeline].current_view();
let timeline = if let ColumnKind::Timeline(timeline) = column.kind_mut() {
timeline
} else {
return 0;
};
let view = timeline.current_view();
let len = view.notes.len();
let mut bar_result: Option<BarResult> = None;
let txn = if let Ok(txn) = Transaction::new(&app.ndb) {
let txn = if let Ok(txn) = Transaction::new(ndb) {
txn
} else {
warn!("failed to create transaction");
return 0;
};
let mut bar_action: Option<(BarAction, Note)> = None;
view.list
.clone()
.borrow_mut()
@@ -77,9 +138,9 @@ fn timeline_ui(ui: &mut egui::Ui, app: &mut Damus, timeline: usize, reversed: bo
start_index
};
let note_key = app.timelines[timeline].current_view().notes[ind].key;
let note_key = timeline.current_view().notes[ind].key;
let note = if let Ok(note) = app.ndb.get_note_by_key(&txn, note_key) {
let note = if let Ok(note) = ndb.get_note_by_key(&txn, note_key) {
note
} else {
warn!("failed to query note {:?}", note_key);
@@ -87,17 +148,13 @@ fn timeline_ui(ui: &mut egui::Ui, app: &mut Damus, timeline: usize, reversed: bo
};
ui::padding(8.0, ui, |ui| {
let textmode = app.textmode;
let resp = ui::NoteView::new(app, &note)
let resp = ui::NoteView::new(ndb, note_cache, img_cache, &note)
.note_previews(!textmode)
.selectable_text(false)
.show(ui);
if let Some(action) = resp.action {
let br = action.execute(app, timeline, note.id(), &txn);
if br.is_some() {
bar_result = br;
}
if let Some(ba) = resp.action {
bar_action = Some((ba, note));
} else if resp.response.clicked() {
debug!("clicked note");
}
@@ -109,15 +166,19 @@ fn timeline_ui(ui: &mut egui::Ui, app: &mut Damus, timeline: usize, reversed: bo
1
});
if let Some(br) = bar_result {
match br {
// update the thread for next render if we have new notes
BarResult::NewThreadNotes(new_notes) => {
let thread = app
.threads
.thread_mut(&app.ndb, &txn, new_notes.root_id.bytes())
.get_ptr();
new_notes.process(thread);
// handle any actions from the virtual list
if let Some((action, note)) = bar_action {
if let Some(br) =
action.execute(ndb, column, threads, note_cache, pool, note.id(), &txn)
{
match br {
// update the thread for next render if we have new notes
BarResult::NewThreadNotes(new_notes) => {
let thread = threads
.thread_mut(ndb, &txn, new_notes.root_id.bytes())
.get_ptr();
new_notes.process(thread);
}
}
}
}
@@ -126,38 +187,27 @@ fn timeline_ui(ui: &mut egui::Ui, app: &mut Damus, timeline: usize, reversed: bo
});
}
fn postbox_view(app: &mut Damus, ui: &mut egui::Ui) {
pub fn postbox_view<'a>(
ndb: &'a Ndb,
key: FilledKeypair<'a>,
pool: &'a mut RelayPool,
drafts: &'a mut Drafts,
img_cache: &'a mut ImageCache,
ui: &'a mut egui::Ui,
) {
// show a postbox in the first timeline
let txn = Transaction::new(ndb).expect("txn");
let response = ui::PostView::new(ndb, drafts.compose_mut(), img_cache, key).ui(&txn, ui);
if let Some(account) = app.account_manager.get_selected_account_index() {
if app
.account_manager
.get_selected_account()
.map_or(false, |a| a.secret_key.is_some())
{
if let Ok(txn) = Transaction::new(&app.ndb) {
let response = ui::PostView::new(app, DraftSource::Compose, account).ui(&txn, ui);
if let Some(action) = response.action {
match action {
PostAction::Post(np) => {
let seckey = app
.account_manager
.get_account(account)
.unwrap()
.secret_key
.as_ref()
.unwrap()
.to_secret_bytes();
let note = np.to_note(&seckey);
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
app.pool.send(&enostr::ClientMessage::raw(raw_msg));
app.drafts.clear(DraftSource::Compose);
}
}
}
if let Some(action) = response.action {
match action {
PostAction::Post(np) => {
let seckey = key.secret_key.to_secret_bytes();
let note = np.to_note(&seckey);
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
pool.send(&enostr::ClientMessage::raw(raw_msg));
drafts.compose_mut().clear();
}
}
}