run cargo fmt
Some checks failed
Rust CI / build-and-test (macOS-latest) (push) Has been cancelled
Rust CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Rust CI / build-and-test (windows-latest) (push) Has been cancelled
Rust CI / cross-compile (macOS-latest, x86_64-pc-windows-gnu) (push) Has been cancelled
Rust CI / cross-compile (macOS-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Rust CI / cross-compile (ubuntu-latest, x86_64-pc-windows-gnu) (push) Has been cancelled
Some checks failed
Rust CI / build-and-test (macOS-latest) (push) Has been cancelled
Rust CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Rust CI / build-and-test (windows-latest) (push) Has been cancelled
Rust CI / cross-compile (macOS-latest, x86_64-pc-windows-gnu) (push) Has been cancelled
Rust CI / cross-compile (macOS-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Rust CI / cross-compile (ubuntu-latest, x86_64-pc-windows-gnu) (push) Has been cancelled
This commit is contained in:
parent
a68e5d5324
commit
1938d68888
7 changed files with 156 additions and 135 deletions
|
@ -1,6 +1,6 @@
|
||||||
use crate::keystorage::{Error, Result, KeyStorage, KeyStorageType};
|
use crate::keystorage::{Error, KeyStorage, KeyStorageType, Result};
|
||||||
use nostr::{Keys, Event};
|
|
||||||
use nostr::nips::nip59::UnwrappedGift;
|
use nostr::nips::nip59::UnwrappedGift;
|
||||||
|
use nostr::{Event, Keys};
|
||||||
use pollster::FutureExt as _;
|
use pollster::FutureExt as _;
|
||||||
|
|
||||||
pub struct AccountManager {
|
pub struct AccountManager {
|
||||||
|
@ -15,12 +15,16 @@ impl AccountManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unwrap_gift_wrap(&mut self, gift_wrap: &Event) -> Result<UnwrappedGift> {
|
pub fn unwrap_gift_wrap(&mut self, gift_wrap: &Event) -> Result<UnwrappedGift> {
|
||||||
let target_pubkey = gift_wrap.tags.iter()
|
let target_pubkey = gift_wrap
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
.find(|tag| tag.kind() == "p".into())
|
.find(|tag| tag.kind() == "p".into())
|
||||||
.and_then(|tag| tag.content())
|
.and_then(|tag| tag.content())
|
||||||
.ok_or(Error::KeyNotFound)?;
|
.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)
|
.find(|key| key.public_key().to_string() == *target_pubkey)
|
||||||
.ok_or(Error::KeyNotFound)?;
|
.ok_or(Error::KeyNotFound)?;
|
||||||
|
|
||||||
|
|
|
@ -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 pollster::FutureExt as _;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ impl MailMessage {
|
||||||
));
|
));
|
||||||
pubkeys_to_send_to.push(*pubkey);
|
pubkeys_to_send_to.push(*pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
for event in &self.parent_events {
|
for event in &self.parent_events {
|
||||||
tags.push(Tag::event(*event));
|
tags.push(Tag::event(*event));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use ewebsock::{WsMessage, WsEvent};
|
use crate::error;
|
||||||
|
use ewebsock::{WsEvent, WsMessage};
|
||||||
use nostr::types::Filter;
|
use nostr::types::Filter;
|
||||||
use nostr::Event;
|
use nostr::Event;
|
||||||
use serde::de::{SeqAccess, Visitor};
|
use serde::de::{SeqAccess, Visitor};
|
||||||
use serde::ser::SerializeSeq;
|
use serde::ser::SerializeSeq;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::fmt::{self};
|
use std::fmt::{self};
|
||||||
use crate::error;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct CommandResult<'a> {
|
pub struct CommandResult<'a> {
|
||||||
|
@ -29,7 +29,7 @@ pub enum RelayEvent<'a> {
|
||||||
Closed,
|
Closed,
|
||||||
Other(&'a WsMessage),
|
Other(&'a WsMessage),
|
||||||
Error(error::Error),
|
Error(error::Error),
|
||||||
Message(RelayMessage<'a>)
|
Message(RelayMessage<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a WsEvent> for RelayEvent<'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(',') {
|
if let Some(comma_index) = msg[start..].find(',') {
|
||||||
let subid_end = start + comma_index;
|
let subid_end = start + comma_index;
|
||||||
let subid = &msg[start..subid_end].trim().trim_matches('"');
|
let subid = &msg[start..subid_end].trim().trim_matches('"');
|
||||||
|
|
||||||
// Find start of event JSON after subscription ID
|
// Find start of event JSON after subscription ID
|
||||||
let event_start = subid_end + 1;
|
let event_start = subid_end + 1;
|
||||||
let mut event_start = event_start;
|
let mut event_start = event_start;
|
||||||
while let Some(&b' ') = msg.as_bytes().get(event_start) {
|
while let Some(&b' ') = msg.as_bytes().get(event_start) {
|
||||||
event_start += 1;
|
event_start += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event JSON goes until end, minus closing bracket
|
// 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));
|
return Ok(Self::event(event_json, subid));
|
||||||
} else {
|
} else {
|
||||||
return Ok(Self::event("{}", "fixme")); // Empty event JSON if parsing fails
|
return Ok(Self::event("{}", "fixme")); // Empty event JSON if parsing fails
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::relay::Subscription;
|
||||||
use crate::relay::{Relay, RelayStatus};
|
use crate::relay::{Relay, RelayStatus};
|
||||||
use ewebsock::{WsEvent, WsMessage};
|
use ewebsock::{WsEvent, WsMessage};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tracing::{error, debug};
|
use std::time::{Duration, Instant};
|
||||||
use std::time::{Instant, Duration};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
pub const RELAY_RECONNECT_SECONDS: u64 = 5;
|
pub const RELAY_RECONNECT_SECONDS: u64 = 5;
|
||||||
|
|
||||||
|
@ -34,7 +34,9 @@ impl RelayPool {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
// Check disconnected relays
|
// 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() {
|
for relay in self.relays.values_mut() {
|
||||||
if relay.status != RelayStatus::Connected {
|
if relay.status != RelayStatus::Connected {
|
||||||
relay.status = RelayStatus::Connecting;
|
relay.status = RelayStatus::Connecting;
|
||||||
|
@ -145,7 +147,11 @@ impl RelayPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pong(m) => {
|
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
|
// who cares
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl ComposeWindow {
|
||||||
let screen_rect = ctx.screen_rect();
|
let screen_rect = ctx.screen_rect();
|
||||||
let min_width = screen_rect.width().min(600.0);
|
let min_width = screen_rect.width().min(600.0);
|
||||||
let min_height = screen_rect.height().min(400.0);
|
let min_height = screen_rect.height().min(400.0);
|
||||||
|
|
||||||
// First collect all window IDs and their minimized state
|
// First collect all window IDs and their minimized state
|
||||||
let state = app
|
let state = app
|
||||||
.state
|
.state
|
||||||
|
@ -29,131 +29,136 @@ impl ComposeWindow {
|
||||||
.get_mut(&id)
|
.get_mut(&id)
|
||||||
.expect("no state found for id");
|
.expect("no state found for id");
|
||||||
|
|
||||||
egui::Window::new("New Message")
|
egui::Window::new("New Message")
|
||||||
.id(id)
|
.id(id)
|
||||||
.default_size([min_width, min_height])
|
.default_size([min_width, min_height])
|
||||||
.min_width(300.0)
|
.min_width(300.0)
|
||||||
.min_height(200.0)
|
.min_height(200.0)
|
||||||
.default_pos([screen_rect.right() - min_width - 20.0, screen_rect.bottom() - min_height - 20.0])
|
.default_pos([
|
||||||
.show(ctx, |ui| {
|
screen_rect.right() - min_width - 20.0,
|
||||||
ui.vertical(|ui| {
|
screen_rect.bottom() - min_height - 20.0,
|
||||||
// Header section
|
])
|
||||||
ui.horizontal(|ui| {
|
.show(ctx, |ui| {
|
||||||
ui.label("To:");
|
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.add_sized(
|
||||||
[ui.available_width(), 24.0],
|
[ui.available_width(), available_height - 20.0],
|
||||||
egui::TextEdit::singleline(&mut state.to_field)
|
egui::TextEdit::multiline(&mut state.content),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
// Bottom bar with account selector and send button
|
||||||
ui.label("Subject:");
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
ui.add_sized(
|
if ui.button("Send").clicked() {
|
||||||
[ui.available_width(), 24.0],
|
if state.selected_account.is_none() {
|
||||||
egui::TextEdit::singleline(&mut state.subject)
|
error!("No Account Selected!");
|
||||||
);
|
return;
|
||||||
});
|
}
|
||||||
|
// convert to field into PublicKey object
|
||||||
|
let to_field = state.to_field.clone();
|
||||||
|
|
||||||
// Toolbar
|
let mut recipient_keys: Vec<PublicKey> = Vec::new();
|
||||||
ui.horizontal(|ui| {
|
for key_string in to_field.split_whitespace() {
|
||||||
ui.style_mut().spacing.button_padding = egui::vec2(4.0, 4.0);
|
use nostr::FromBech32;
|
||||||
if ui.button("B").clicked() {}
|
match PublicKey::from_bech32(key_string) {
|
||||||
if ui.button("I").clicked() {}
|
Ok(k) => recipient_keys.push(k),
|
||||||
if ui.button("U").clicked() {}
|
Err(e) => debug!("could not parse public key as bech32: {}", e),
|
||||||
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![],
|
|
||||||
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
|
match PublicKey::from_hex(key_string) {
|
||||||
for event in events_to_send {
|
Ok(k) => recipient_keys.push(k),
|
||||||
match serde_json::to_string(&ClientMessage::Event { event: event.1 }) {
|
Err(e) => debug!("could not parse public key as hex: {}", e),
|
||||||
Ok(v) => match app.relays.send(ewebsock::WsMessage::Text(v)) {
|
};
|
||||||
Ok(r) => r,
|
}
|
||||||
Err(e) => error!("could not send event to relays: {}", e),
|
|
||||||
},
|
let mut msg = MailMessage {
|
||||||
Err(e) => error!("could not serialize event: {}", e),
|
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
|
// Keep the original show method for backward compatibility
|
||||||
|
|
|
@ -51,7 +51,12 @@ impl OnboardingScreen {
|
||||||
|
|
||||||
// here, we are assuming that the most recent key added is the one that was generated in
|
// here, we are assuming that the most recent key added is the one that was generated in
|
||||||
// onboarding_new()'s button click.
|
// 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!(
|
ui.label(format!(
|
||||||
"New identity: {}",
|
"New identity: {}",
|
||||||
first_key.public_key().to_bech32().unwrap()
|
first_key.public_key().to_bech32().unwrap()
|
||||||
|
|
|
@ -107,7 +107,8 @@ impl SettingsScreen {
|
||||||
// TODO: this only updates when next frame is rendered, which can be more than
|
// 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.
|
// a few seconds between renders. Make it so it updates every second.
|
||||||
if relay.status == crate::relay::RelayStatus::Disconnected {
|
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));
|
ui.label(format!("(Attempting reconnect in {} seconds)", next_ping));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue