diff --git a/src/account_manager.rs b/src/account_manager.rs index 299867f..c40f588 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -1,6 +1,6 @@ -use crate::keystorage::{Error, Result, KeyStorage, KeyStorageType}; -use nostr::{Keys, Event}; +use crate::keystorage::{Error, KeyStorage, KeyStorageType, Result}; use nostr::nips::nip59::UnwrappedGift; +use nostr::{Event, Keys}; use pollster::FutureExt as _; pub struct AccountManager { @@ -15,12 +15,16 @@ impl AccountManager { } pub fn unwrap_gift_wrap(&mut self, gift_wrap: &Event) -> Result { - let target_pubkey = gift_wrap.tags.iter() + let target_pubkey = gift_wrap + .tags + .iter() .find(|tag| tag.kind() == "p".into()) .and_then(|tag| tag.content()) .ok_or(Error::KeyNotFound)?; - let target_key = self.loaded_keys.iter() + let target_key = self + .loaded_keys + .iter() .find(|key| key.public_key().to_string() == *target_pubkey) .ok_or(Error::KeyNotFound)?; diff --git a/src/mail_event.rs b/src/mail_event.rs index 9f0ef13..20c9199 100644 --- a/src/mail_event.rs +++ b/src/mail_event.rs @@ -1,4 +1,4 @@ -use nostr::{Event, EventBuilder, Keys, Kind, PublicKey, Tag, TagKind, TagStandard, EventId}; +use nostr::{Event, EventBuilder, EventId, Keys, Kind, PublicKey, Tag, TagKind, TagStandard}; use pollster::FutureExt as _; use std::collections::HashMap; @@ -31,7 +31,7 @@ impl MailMessage { )); pubkeys_to_send_to.push(*pubkey); } - + for event in &self.parent_events { tags.push(Tag::event(*event)); } diff --git a/src/relay/message.rs b/src/relay/message.rs index 57805a6..d99371c 100644 --- a/src/relay/message.rs +++ b/src/relay/message.rs @@ -1,11 +1,11 @@ -use ewebsock::{WsMessage, WsEvent}; +use crate::error; +use ewebsock::{WsEvent, WsMessage}; use nostr::types::Filter; use nostr::Event; use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{self}; -use crate::error; #[derive(Debug, Eq, PartialEq)] pub struct CommandResult<'a> { @@ -29,7 +29,7 @@ pub enum RelayEvent<'a> { Closed, Other(&'a WsMessage), Error(error::Error), - Message(RelayMessage<'a>) + Message(RelayMessage<'a>), } impl<'a> From<&'a WsEvent> for RelayEvent<'a> { @@ -104,17 +104,17 @@ impl<'a> RelayMessage<'a> { if let Some(comma_index) = msg[start..].find(',') { let subid_end = start + comma_index; let subid = &msg[start..subid_end].trim().trim_matches('"'); - + // Find start of event JSON after subscription ID let event_start = subid_end + 1; let mut event_start = event_start; while let Some(&b' ') = msg.as_bytes().get(event_start) { event_start += 1; } - + // Event JSON goes until end, minus closing bracket - let event_json = &msg[event_start..msg.len()-1]; - + let event_json = &msg[event_start..msg.len() - 1]; + return Ok(Self::event(event_json, subid)); } else { return Ok(Self::event("{}", "fixme")); // Empty event JSON if parsing fails diff --git a/src/relay/pool.rs b/src/relay/pool.rs index 9ac7228..b2a4d06 100644 --- a/src/relay/pool.rs +++ b/src/relay/pool.rs @@ -4,8 +4,8 @@ use crate::relay::Subscription; use crate::relay::{Relay, RelayStatus}; use ewebsock::{WsEvent, WsMessage}; use std::collections::HashMap; -use tracing::{error, debug}; -use std::time::{Instant, Duration}; +use std::time::{Duration, Instant}; +use tracing::{debug, error}; pub const RELAY_RECONNECT_SECONDS: u64 = 5; @@ -34,7 +34,9 @@ impl RelayPool { let now = Instant::now(); // Check disconnected relays - if now.duration_since(self.last_reconnect_attempt) >= Duration::from_secs(RELAY_RECONNECT_SECONDS) { + if now.duration_since(self.last_reconnect_attempt) + >= Duration::from_secs(RELAY_RECONNECT_SECONDS) + { for relay in self.relays.values_mut() { if relay.status != RelayStatus::Connected { relay.status = RelayStatus::Connecting; @@ -145,7 +147,11 @@ impl RelayPool { } } Pong(m) => { - debug!("pong recieved from {} after approx {} seconds", &url, self.last_ping.elapsed().as_secs()); + debug!( + "pong recieved from {} after approx {} seconds", + &url, + self.last_ping.elapsed().as_secs() + ); } _ => { // who cares diff --git a/src/ui/compose_window.rs b/src/ui/compose_window.rs index 588f86d..be4986d 100644 --- a/src/ui/compose_window.rs +++ b/src/ui/compose_window.rs @@ -21,7 +21,7 @@ impl ComposeWindow { 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 state = app .state @@ -29,131 +29,136 @@ impl ComposeWindow { .get_mut(&id) .expect("no state found for id"); - egui::Window::new("New Message") - .id(id) - .default_size([min_width, min_height]) - .min_width(300.0) - .min_height(200.0) - .default_pos([screen_rect.right() - min_width - 20.0, screen_rect.bottom() - min_height - 20.0]) - .show(ctx, |ui| { - ui.vertical(|ui| { - // Header section - ui.horizontal(|ui| { - ui.label("To:"); + egui::Window::new("New Message") + .id(id) + .default_size([min_width, min_height]) + .min_width(300.0) + .min_height(200.0) + .default_pos([ + screen_rect.right() - min_width - 20.0, + screen_rect.bottom() - min_height - 20.0, + ]) + .show(ctx, |ui| { + ui.vertical(|ui| { + // 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(), 24.0], - egui::TextEdit::singleline(&mut state.to_field) + [ui.available_width(), available_height - 20.0], + egui::TextEdit::multiline(&mut state.content), ); }); - ui.horizontal(|ui| { - ui.label("Subject:"); - ui.add_sized( - [ui.available_width(), 24.0], - egui::TextEdit::singleline(&mut state.subject) - ); - }); + // 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(); - // 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 = 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![], - parent_events: state.parent_events.clone(), - subject: state.subject.clone(), - content: state.content.clone(), + let mut recipient_keys: Vec = 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), }; - let events_to_send = - msg.to_events(&state.selected_account.clone().unwrap()); - // 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), - }; + 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![], + parent_events: state.parent_events.clone(), + subject: state.subject.clone(), + content: state.content.clone(), + }; + let events_to_send = + msg.to_events(&state.selected_account.clone().unwrap()); + + // 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(), + ); } - } - - // 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(), - ); - } - }); - }); + }); }); }); + }); } // Keep the original show method for backward compatibility diff --git a/src/ui/onboarding.rs b/src/ui/onboarding.rs index 25416c2..42d9b0e 100644 --- a/src/ui/onboarding.rs +++ b/src/ui/onboarding.rs @@ -51,7 +51,12 @@ impl OnboardingScreen { // here, we are assuming that the most recent key added is the one that was generated in // onboarding_new()'s button click. - let first_key = app.account_manager.loaded_keys.last().expect("wanted a key from last screen").clone(); + let first_key = app + .account_manager + .loaded_keys + .last() + .expect("wanted a key from last screen") + .clone(); ui.label(format!( "New identity: {}", first_key.public_key().to_bech32().unwrap() diff --git a/src/ui/settings.rs b/src/ui/settings.rs index 3e87389..29118fa 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -107,7 +107,8 @@ impl SettingsScreen { // TODO: this only updates when next frame is rendered, which can be more than // a few seconds between renders. Make it so it updates every second. if relay.status == crate::relay::RelayStatus::Disconnected { - let next_ping = crate::relay::RELAY_RECONNECT_SECONDS - last_ping.elapsed().as_secs(); + let next_ping = + crate::relay::RELAY_RECONNECT_SECONDS - last_ping.elapsed().as_secs(); ui.label(format!("(Attempting reconnect in {} seconds)", next_ping)); }