commit
eefd019e9b
2 changed files with 407 additions and 223 deletions
338
src/main.rs
338
src/main.rs
|
@ -3,6 +3,7 @@
|
|||
use eframe::egui::{self, FontDefinitions, Sense, Vec2b};
|
||||
use egui::FontFamily::Proportional;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use relay::RelayMessage;
|
||||
use std::collections::HashMap;
|
||||
use tracing::{debug, error, info, Level};
|
||||
|
||||
|
@ -151,177 +152,244 @@ fn process_event(app: &mut Hoot, _sub_id: &str, event_json: &str) {
|
|||
|
||||
// Parse the event using the RelayMessage type which handles the ["EVENT", subscription_id, event_json] format
|
||||
if let Ok(event) = serde_json::from_str::<nostr::Event>(event_json) {
|
||||
// Verify the event signature
|
||||
if event.verify().is_ok() {
|
||||
debug!("Verified event: {:?}", event);
|
||||
app.events.push(event);
|
||||
} else {
|
||||
error!("Event verification failed");
|
||||
}
|
||||
// Verify the event signature
|
||||
if event.verify().is_ok() {
|
||||
debug!("Verified event: {:?}", event);
|
||||
app.events.push(event);
|
||||
} else {
|
||||
error!("Event verification failed");
|
||||
}
|
||||
} else {
|
||||
error!("Failed to parse event JSON: {}", event_json);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_app(app: &mut Hoot, ctx: &egui::Context) {
|
||||
#[cfg(feature = "profiling")]
|
||||
puffin::profile_function!();
|
||||
// Render compose windows if any are open - moved outside CentralPanel
|
||||
for window_id in app.state.compose_window.clone().into_keys() {
|
||||
ui::compose_window::ComposeWindow::show_window(app, ctx, window_id);
|
||||
}
|
||||
|
||||
if app.page == Page::Onboarding
|
||||
|| app.page == Page::OnboardingNew
|
||||
|| app.page == Page::OnboardingNewShowKey
|
||||
|| app.page == Page::OnboardingReturning
|
||||
{
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui::onboarding::OnboardingScreen::ui(app, ui);
|
||||
});
|
||||
} else {
|
||||
egui::SidePanel::left("Side Navbar").show(ctx, |ui| {
|
||||
ui.heading("Hoot");
|
||||
if ui.button("Inbox").clicked() {
|
||||
app.page = Page::Inbox;
|
||||
}
|
||||
if ui.button("Drafts").clicked() {
|
||||
app.page = Page::Drafts;
|
||||
}
|
||||
if ui.button("Settings").clicked() {
|
||||
app.page = Page::Settings;
|
||||
}
|
||||
if ui.button("Onboarding").clicked() {
|
||||
app.page = Page::Onboarding;
|
||||
}
|
||||
});
|
||||
egui::SidePanel::left("left_panel")
|
||||
.default_width(200.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(8.0);
|
||||
// App title
|
||||
ui.heading("Hoot");
|
||||
ui.add_space(16.0);
|
||||
|
||||
egui::TopBottomPanel::top("Search").show(ctx, |ui| {
|
||||
ui.heading("Search");
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// todo: fix
|
||||
for window_id in app.state.compose_window.clone().into_keys() {
|
||||
ui::compose_window::ComposeWindow::show(app, ui, window_id);
|
||||
}
|
||||
|
||||
if app.page == Page::Inbox {
|
||||
ui.label("hello there!");
|
||||
if ui.button("Compose").clicked() {
|
||||
// Compose button
|
||||
if ui.add_sized(
|
||||
[180.0, 36.0],
|
||||
egui::Button::new("✉ Compose").fill(egui::Color32::from_rgb(149, 117, 205)),
|
||||
).clicked() {
|
||||
let state = ui::compose_window::ComposeWindowState {
|
||||
subject: String::new(),
|
||||
to_field: String::new(),
|
||||
content: String::new(),
|
||||
selected_account: None,
|
||||
minimized: false,
|
||||
};
|
||||
app.state
|
||||
.compose_window
|
||||
.insert(egui::Id::new(rand::random::<u32>()), state);
|
||||
}
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
// Navigation items
|
||||
let nav_items = [
|
||||
("📥 Inbox", Page::Inbox, app.events.len()),
|
||||
("🔄 Requests", Page::Post, 20),
|
||||
("📝 Drafts", Page::Drafts, 3),
|
||||
("⭐ Starred", Page::Post, 0),
|
||||
("📁 Archived", Page::Post, 0),
|
||||
("🗑️ Trash", Page::Post, 0),
|
||||
];
|
||||
|
||||
if ui.button("Send Test Event").clicked() {
|
||||
let temp_keys = nostr::Keys::generate();
|
||||
// todo: lmao
|
||||
let new_event = nostr::EventBuilder::text_note("GFY!")
|
||||
.sign_with_keys(&temp_keys)
|
||||
.unwrap();
|
||||
let event_json = crate::relay::ClientMessage::Event { event: new_event };
|
||||
let _ = &app
|
||||
.relays
|
||||
.send(ewebsock::WsMessage::Text(
|
||||
serde_json::to_string(&event_json).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
for (label, page, count) in nav_items {
|
||||
let is_selected = app.page == page;
|
||||
let response = ui.selectable_label(is_selected, format!("{} {}", label, if count > 0 { count.to_string() } else { String::new() }));
|
||||
if response.clicked() {
|
||||
app.page = page;
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Get kind 1 notes").clicked() {
|
||||
let mut filter = nostr::types::Filter::new();
|
||||
filter = filter.kind(nostr::Kind::TextNote);
|
||||
let mut sub = crate::relay::Subscription::default();
|
||||
sub.filter(filter);
|
||||
let c_msg = crate::relay::ClientMessage::from(sub);
|
||||
|
||||
let _ = &app
|
||||
.relays
|
||||
.send(ewebsock::WsMessage::Text(
|
||||
serde_json::to_string(&c_msg).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
ui.label(format!("total events rendered: {}", app.events.len()));
|
||||
// Add flexible space to push profile to bottom
|
||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
||||
ui.add_space(8.0);
|
||||
// Profile section
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add_sized([32.0, 32.0], egui::Button::new("👤")).clicked() {
|
||||
app.page = Page::Settings;
|
||||
}
|
||||
if let Some(key) = app.account_manager.loaded_keys.first() {
|
||||
ui.label(&key.public_key().to_string()[..8]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
match app.page {
|
||||
Page::Inbox => {
|
||||
// Top bar with search
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(16.0);
|
||||
let search_width = ui.available_width() - 100.0;
|
||||
ui.add_sized(
|
||||
[search_width, 32.0],
|
||||
egui::TextEdit::singleline(&mut String::new())
|
||||
.hint_text("Search")
|
||||
.margin(egui::vec2(8.0, 4.0))
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Email list using TableBuilder
|
||||
TableBuilder::new(ui)
|
||||
.column(Column::auto())
|
||||
.column(Column::auto())
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::auto()) // Checkbox
|
||||
.column(Column::auto()) // Star
|
||||
.column(Column::remainder()) // Sender
|
||||
.column(Column::remainder()) // Content
|
||||
.column(Column::remainder()) // Time
|
||||
.striped(true)
|
||||
.sense(Sense::click())
|
||||
.auto_shrink(Vec2b { x: false, y: false })
|
||||
.header(20.0, |_header| {})
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.checkbox(&mut false, "");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.label("⭐");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.label("From");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.label("Content");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.label("Time");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
let row_height = 30.0;
|
||||
let events = app.events.clone();
|
||||
body.rows(row_height, events.len(), |mut row| {
|
||||
let event = &events[row.index()];
|
||||
row.col(|ui| {
|
||||
ui.checkbox(&mut false, "");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.checkbox(&mut false, "");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(event.pubkey.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(event.content.clone());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label("2 minutes ago");
|
||||
});
|
||||
|
||||
// Try to unwrap the gift wrap
|
||||
if let Ok(unwrapped) = app.account_manager.unwrap_gift_wrap(event) {
|
||||
row.col(|ui| {
|
||||
ui.checkbox(&mut false, "");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.checkbox(&mut false, "");
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(unwrapped.sender.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
// Try to get subject from tags
|
||||
let subject = match &unwrapped.rumor.tags.find(nostr::TagKind::Subject) {
|
||||
Some(s) => match s.content() {
|
||||
Some(c) => format!("{}: {}", c.to_string(), unwrapped.rumor.content),
|
||||
None => unwrapped.rumor.content.clone(),
|
||||
},
|
||||
None => unwrapped.rumor.content.clone(),
|
||||
};
|
||||
ui.label(subject);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label("2 minutes ago");
|
||||
});
|
||||
|
||||
if row.response().clicked() {
|
||||
println!("clicked: {}", event.content.clone());
|
||||
app.focused_post = event.id.to_string();
|
||||
app.page = Page::Post;
|
||||
if row.response().clicked() {
|
||||
app.focused_post = event.id.to_string();
|
||||
app.page = Page::Post;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if app.page == Page::Settings {
|
||||
ui.heading("Settings");
|
||||
ui::settings::SettingsScreen::ui(app, ui);
|
||||
} else if app.page == Page::Post {
|
||||
assert!(
|
||||
!app.focused_post.is_empty(),
|
||||
"focused_post should not be empty when Page::Post"
|
||||
);
|
||||
|
||||
let gift_wrapped_event = app
|
||||
.events
|
||||
.iter()
|
||||
.find(|&x| x.id.to_string() == app.focused_post)
|
||||
.expect("event id should be present inside event list");
|
||||
|
||||
let event_to_display = app.account_manager.unwrap_gift_wrap(gift_wrapped_event).expect("we should be able to unwrap an event we recieved");
|
||||
|
||||
ui.heading("View Message");
|
||||
ui.label(format!("Content: {}", event_to_display.rumor.content));
|
||||
ui.label(match &event_to_display.rumor.tags.find(nostr::TagKind::Subject) {
|
||||
Some(s) => match s.content() {
|
||||
Some(c) => format!("Subject: {}", c.to_string()),
|
||||
None => "Subject: None".to_string(),
|
||||
},
|
||||
None => "Subject: None".to_string(),
|
||||
});
|
||||
|
||||
ui.label(match &event_to_display.rumor.id {
|
||||
Some(id) => format!("ID: {}", id.to_string()),
|
||||
None => "ID: None".to_string(),
|
||||
});
|
||||
|
||||
ui.label(format!("Author: {}", event_to_display.sender.to_string()));
|
||||
}
|
||||
});
|
||||
}
|
||||
Page::Settings => {
|
||||
ui::settings::SettingsScreen::ui(app, ui);
|
||||
}
|
||||
Page::Post => {
|
||||
if let Some(event) = app.events.iter().find(|e| e.id.to_string() == app.focused_post) {
|
||||
if let Ok(unwrapped) = app.account_manager.unwrap_gift_wrap(event) {
|
||||
// Message header section
|
||||
ui.add_space(8.0);
|
||||
ui.heading(&unwrapped.rumor.tags.find(nostr::TagKind::Subject)
|
||||
.and_then(|s| s.content())
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or_else(|| "No Subject".to_string()));
|
||||
|
||||
// Metadata grid
|
||||
egui::Grid::new("email_metadata")
|
||||
.num_columns(2)
|
||||
.spacing([8.0, 4.0])
|
||||
.show(ui, |ui| {
|
||||
ui.label("From");
|
||||
ui.label(unwrapped.sender.to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("To");
|
||||
ui.label(unwrapped.rumor.tags.iter()
|
||||
.filter_map(|tag| tag.content())
|
||||
.next()
|
||||
.unwrap_or_else(|| "Unknown")
|
||||
.to_string());
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Action buttons
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("📎 Attach").clicked() {
|
||||
// TODO: Handle attachment
|
||||
}
|
||||
if ui.button("📝 Edit").clicked() {
|
||||
// TODO: Handle edit
|
||||
}
|
||||
if ui.button("🗑️ Delete").clicked() {
|
||||
// TODO: Handle delete
|
||||
}
|
||||
if ui.button("↩️ Reply").clicked() {
|
||||
// TODO: Handle reply
|
||||
}
|
||||
if ui.button("↪️ Forward").clicked() {
|
||||
// TODO: Handle forward
|
||||
}
|
||||
if ui.button("⭐ Star").clicked() {
|
||||
// TODO: Handle star
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
ui.separator();
|
||||
ui.add_space(16.0);
|
||||
|
||||
// Message content
|
||||
ui.label(&unwrapped.rumor.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
Page::Drafts => {
|
||||
ui.heading("Drafts");
|
||||
ui.label("Your draft messages will appear here");
|
||||
}
|
||||
_ => {
|
||||
ui.heading("Coming Soon");
|
||||
ui.label("This feature is under development");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Hoot {
|
||||
|
|
|
@ -10,107 +10,223 @@ pub struct ComposeWindowState {
|
|||
pub to_field: String,
|
||||
pub content: String,
|
||||
pub selected_account: Option<Keys>,
|
||||
pub minimized: bool,
|
||||
}
|
||||
|
||||
pub struct ComposeWindow {}
|
||||
|
||||
impl ComposeWindow {
|
||||
pub fn show(app: &mut crate::Hoot, ui: &mut egui::Ui, id: egui::Id) {
|
||||
pub fn show_window(app: &mut crate::Hoot, ctx: &egui::Context, id: egui::Id) {
|
||||
let screen_rect = ctx.screen_rect();
|
||||
let min_width = screen_rect.width().min(600.0);
|
||||
let min_height = screen_rect.height().min(400.0);
|
||||
|
||||
// First collect all window IDs and their minimized state
|
||||
let window_states: Vec<_> = app.state.compose_window.iter()
|
||||
.map(|(id, state)| (*id, state.minimized))
|
||||
.collect();
|
||||
|
||||
let mut should_close = false;
|
||||
|
||||
let state = app
|
||||
.state
|
||||
.compose_window
|
||||
.get_mut(&id)
|
||||
.expect("no state found for id");
|
||||
egui::Window::new(&state.subject)
|
||||
.id(id)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.label("Hello!");
|
||||
ui.vertical(|ui| {
|
||||
|
||||
if state.minimized {
|
||||
// Count how many minimized windows are before this one
|
||||
let minimized_index = window_states.iter()
|
||||
.filter(|(other_id, is_minimized)| {
|
||||
*is_minimized && other_id.value() < id.value()
|
||||
})
|
||||
.count();
|
||||
|
||||
let minimized_height = 32.0;
|
||||
let minimized_width = 200.0;
|
||||
let minimized_spacing = 2.0;
|
||||
let stack_on_left = screen_rect.width() < min_width + minimized_width + 40.0;
|
||||
|
||||
let window_y_offset = -(minimized_height + minimized_spacing) * (minimized_index as f32 + 1.0);
|
||||
|
||||
let (anchor, anchor_pos) = if stack_on_left {
|
||||
(egui::Align2::LEFT_BOTTOM, [20.0, window_y_offset])
|
||||
} else {
|
||||
(egui::Align2::RIGHT_BOTTOM, [-20.0, window_y_offset])
|
||||
};
|
||||
|
||||
egui::Window::new("📧")
|
||||
.id(id)
|
||||
.fixed_size([minimized_width, minimized_height])
|
||||
.anchor(anchor, anchor_pos)
|
||||
.title_bar(true)
|
||||
.frame(egui::Frame::window(&ctx.style()).multiply_with_opacity(0.95))
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("To:");
|
||||
ui.text_edit_singleline(&mut state.to_field);
|
||||
});
|
||||
|
||||
{
|
||||
// god this is such a fucking mess
|
||||
let accounts = app.account_manager.loaded_keys.clone();
|
||||
use nostr::ToBech32;
|
||||
let mut formatted_key = String::new();
|
||||
if state.selected_account.is_some() {
|
||||
formatted_key = state
|
||||
.selected_account
|
||||
.clone()
|
||||
.unwrap()
|
||||
.public_key()
|
||||
.to_bech32()
|
||||
.unwrap();
|
||||
if ui.button("📧").clicked() {
|
||||
state.minimized = false;
|
||||
}
|
||||
|
||||
egui::ComboBox::from_label("Select Keys to Send With")
|
||||
.selected_text(format!("{}", formatted_key))
|
||||
.show_ui(ui, |ui| {
|
||||
for key in accounts {
|
||||
ui.selectable_value(
|
||||
&mut state.selected_account,
|
||||
Some(key.clone()),
|
||||
key.public_key().to_bech32().unwrap(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Subject:");
|
||||
ui.text_edit_singleline(&mut state.subject);
|
||||
});
|
||||
ui.label("Body:");
|
||||
ui.text_edit_multiline(&mut state.content);
|
||||
|
||||
if ui.button("Send").clicked() {
|
||||
if state.selected_account.is_none() {
|
||||
error!("No Account Selected!");
|
||||
return;
|
||||
}
|
||||
// convert to field into PublicKey object
|
||||
let to_field = state.to_field.clone();
|
||||
|
||||
let mut recipient_keys: Vec<PublicKey> = Vec::new();
|
||||
for key_string in to_field.split_whitespace() {
|
||||
use nostr::FromBech32;
|
||||
match PublicKey::from_bech32(key_string) {
|
||||
Ok(k) => recipient_keys.push(k),
|
||||
Err(e) => debug!("could not parse public key as bech32: {}", e),
|
||||
};
|
||||
|
||||
match PublicKey::from_hex(key_string) {
|
||||
Ok(k) => recipient_keys.push(k),
|
||||
Err(e) => debug!("could not parse public key as hex: {}", e),
|
||||
};
|
||||
}
|
||||
|
||||
let mut msg = MailMessage {
|
||||
to: recipient_keys,
|
||||
cc: vec![],
|
||||
bcc: vec![],
|
||||
subject: state.subject.clone(),
|
||||
content: state.content.clone(),
|
||||
let subject = state.subject.as_str();
|
||||
let display_text = if subject.is_empty() {
|
||||
"New Message"
|
||||
} else {
|
||||
subject.get(0..20).unwrap_or(subject)
|
||||
};
|
||||
let events_to_send =
|
||||
msg.to_events(&state.selected_account.clone().unwrap());
|
||||
|
||||
info!("new events! {:?}", events_to_send);
|
||||
// send over wire
|
||||
for event in events_to_send {
|
||||
match serde_json::to_string(&ClientMessage::Event { event: event.1 }) {
|
||||
Ok(v) => match app.relays.send(ewebsock::WsMessage::Text(v)) {
|
||||
Ok(r) => r,
|
||||
Err(e) => error!("could not send event to relays: {}", e),
|
||||
},
|
||||
Err(e) => error!("could not serialize event: {}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
ui.label(RichText::new(display_text).size(11.0));
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
egui::Window::new("New Message")
|
||||
.id(id)
|
||||
.default_size([min_width, min_height])
|
||||
.min_width(300.0)
|
||||
.min_height(200.0)
|
||||
.anchor(egui::Align2::RIGHT_BOTTOM, [-20.0, -20.0])
|
||||
.default_pos([screen_rect.right() - min_width - 20.0, screen_rect.bottom() - min_height - 20.0])
|
||||
.collapsible(false)
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
// Window controls at the top
|
||||
ui.horizontal(|ui| {
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("❌").clicked() {
|
||||
should_close = true;
|
||||
}
|
||||
if ui.button("🗕").clicked() {
|
||||
state.minimized = true;
|
||||
}
|
||||
ui.add_space(ui.available_width() - 50.0); // Push buttons to the right
|
||||
});
|
||||
});
|
||||
|
||||
// Header section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("To:");
|
||||
ui.add_sized(
|
||||
[ui.available_width(), 24.0],
|
||||
egui::TextEdit::singleline(&mut state.to_field)
|
||||
);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Subject:");
|
||||
ui.add_sized(
|
||||
[ui.available_width(), 24.0],
|
||||
egui::TextEdit::singleline(&mut state.subject)
|
||||
);
|
||||
});
|
||||
|
||||
// Toolbar
|
||||
ui.horizontal(|ui| {
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(4.0, 4.0);
|
||||
if ui.button("B").clicked() {}
|
||||
if ui.button("I").clicked() {}
|
||||
if ui.button("U").clicked() {}
|
||||
ui.separator();
|
||||
if ui.button("🔗").clicked() {}
|
||||
if ui.button("📎").clicked() {}
|
||||
if ui.button("😀").clicked() {}
|
||||
ui.separator();
|
||||
if ui.button("⌄").clicked() {}
|
||||
});
|
||||
|
||||
// Message content
|
||||
let available_height = ui.available_height() - 40.0; // Reserve space for bottom bar
|
||||
egui::ScrollArea::vertical()
|
||||
.max_height(available_height)
|
||||
.show(ui, |ui| {
|
||||
ui.add_sized(
|
||||
[ui.available_width(), available_height - 20.0],
|
||||
egui::TextEdit::multiline(&mut state.content)
|
||||
);
|
||||
});
|
||||
|
||||
// Bottom bar with account selector and send button
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Send").clicked() {
|
||||
if state.selected_account.is_none() {
|
||||
error!("No Account Selected!");
|
||||
return;
|
||||
}
|
||||
// convert to field into PublicKey object
|
||||
let to_field = state.to_field.clone();
|
||||
|
||||
let mut recipient_keys: Vec<PublicKey> = Vec::new();
|
||||
for key_string in to_field.split_whitespace() {
|
||||
use nostr::FromBech32;
|
||||
match PublicKey::from_bech32(key_string) {
|
||||
Ok(k) => recipient_keys.push(k),
|
||||
Err(e) => debug!("could not parse public key as bech32: {}", e),
|
||||
};
|
||||
|
||||
match PublicKey::from_hex(key_string) {
|
||||
Ok(k) => recipient_keys.push(k),
|
||||
Err(e) => debug!("could not parse public key as hex: {}", e),
|
||||
};
|
||||
}
|
||||
|
||||
let mut msg = MailMessage {
|
||||
to: recipient_keys,
|
||||
cc: vec![],
|
||||
bcc: vec![],
|
||||
subject: state.subject.clone(),
|
||||
content: state.content.clone(),
|
||||
};
|
||||
let events_to_send =
|
||||
msg.to_events(&state.selected_account.clone().unwrap());
|
||||
|
||||
info!("new events! {:?}", events_to_send);
|
||||
// send over wire
|
||||
for event in events_to_send {
|
||||
match serde_json::to_string(&ClientMessage::Event { event: event.1 }) {
|
||||
Ok(v) => match app.relays.send(ewebsock::WsMessage::Text(v)) {
|
||||
Ok(r) => r,
|
||||
Err(e) => error!("could not send event to relays: {}", e),
|
||||
},
|
||||
Err(e) => error!("could not serialize event: {}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Account selector
|
||||
let accounts = app.account_manager.loaded_keys.clone();
|
||||
use nostr::ToBech32;
|
||||
let mut formatted_key = String::new();
|
||||
if state.selected_account.is_some() {
|
||||
formatted_key = state
|
||||
.selected_account
|
||||
.clone()
|
||||
.unwrap()
|
||||
.public_key()
|
||||
.to_bech32()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
egui::ComboBox::from_id_source("account_selector")
|
||||
.selected_text(format!("{}", formatted_key))
|
||||
.show_ui(ui, |ui| {
|
||||
for key in accounts {
|
||||
ui.selectable_value(
|
||||
&mut state.selected_account,
|
||||
Some(key.clone()),
|
||||
key.public_key().to_bech32().unwrap(),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if should_close {
|
||||
app.state.compose_window.remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the original show method for backward compatibility
|
||||
pub fn show(app: &mut crate::Hoot, ui: &mut egui::Ui, id: egui::Id) {
|
||||
Self::show_window(app, ui.ctx(), id);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue