mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 08:21:47 +00:00
Merge branch 'dmchat' into unstable
This commit is contained in:
commit
0eb3bb7c59
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -1871,7 +1871,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gossip-relay-picker"
|
||||
version = "0.2.0-unstable"
|
||||
source = "git+https://github.com/mikedilger/gossip-relay-picker?rev=7e33f6c35ce0147310545cc984eb263cd96eb380#7e33f6c35ce0147310545cc984eb263cd96eb380"
|
||||
source = "git+https://github.com/mikedilger/gossip-relay-picker?rev=1dc2d9a8e9bce1cf54c6ab4cdbba2f2b317cbfc7#1dc2d9a8e9bce1cf54c6ab4cdbba2f2b317cbfc7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dashmap",
|
||||
@ -2698,7 +2698,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr-types"
|
||||
version = "0.7.0-unstable"
|
||||
source = "git+https://github.com/mikedilger/nostr-types?rev=232713f21141c0a7331a6a7a93bcc9343dfbb3d9#232713f21141c0a7331a6a7a93bcc9343dfbb3d9"
|
||||
source = "git+https://github.com/mikedilger/nostr-types?rev=0df20de534cf226910d382087a30880039372ef2#0df20de534cf226910d382087a30880039372ef2"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64 0.21.2",
|
||||
|
@ -38,7 +38,7 @@ fallible-iterator = "0.2"
|
||||
filetime = "0.2"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "7e33f6c35ce0147310545cc984eb263cd96eb380" }
|
||||
gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "1dc2d9a8e9bce1cf54c6ab4cdbba2f2b317cbfc7" }
|
||||
heed = { git = "https://github.com/meilisearch/heed", rev = "02030e3bf3d26ee98d4f5343fc086a7b63289159" }
|
||||
hex = "0.4"
|
||||
http = "0.2"
|
||||
@ -49,7 +49,7 @@ lazy_static = "1.4"
|
||||
linkify = "0.9"
|
||||
memoize = "0.4"
|
||||
mime = "0.3"
|
||||
nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "232713f21141c0a7331a6a7a93bcc9343dfbb3d9", features = [ "speedy" ] }
|
||||
nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "0df20de534cf226910d382087a30880039372ef2", features = [ "speedy" ] }
|
||||
parking_lot = "0.12"
|
||||
paste = "1.0"
|
||||
qrcode = { git = "https://github.com/mikedilger/qrcode-rust", rev = "519b77b3efa3f84961169b47d3de08c5ddd86548" }
|
||||
|
@ -197,7 +197,6 @@ pub fn events_of_pubkey_and_kind(mut args: env::Args) -> Result<(), Error> {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let kind: EventKind = match args.next() {
|
||||
Some(integer) => integer.parse::<u32>()?.into(),
|
||||
None => {
|
||||
|
@ -78,7 +78,7 @@ pub enum ToMinionPayloadDetail {
|
||||
PullFollowing,
|
||||
Shutdown,
|
||||
SubscribeAugments(Vec<IdHex>),
|
||||
SubscribeConfig,
|
||||
SubscribeOutbox,
|
||||
SubscribeDiscover(Vec<PublicKey>),
|
||||
SubscribeGeneralFeed(Vec<PublicKey>),
|
||||
SubscribeMentions,
|
||||
|
57
src/dm_channel.rs
Normal file
57
src/dm_channel.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use nostr_types::{PublicKey, Unixtime};
|
||||
use sha2::Digest;
|
||||
|
||||
/// This represents a DM (direct message) channel which includes a set
|
||||
/// of participants (usually just one, but can be a small group).
|
||||
// internally the pubkeys are kept sorted so they can be compared
|
||||
// that is why we don't expose the inner field directly.
|
||||
//
|
||||
// The pubkey of the gossip user is not included. If they send themselves
|
||||
// a note, that channel has an empty vec.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DmChannel(Vec<PublicKey>);
|
||||
|
||||
impl DmChannel {
|
||||
pub fn new(public_keys: &[PublicKey]) -> DmChannel {
|
||||
let mut vec = public_keys.to_owned();
|
||||
vec.sort();
|
||||
vec.dedup();
|
||||
DmChannel(vec)
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> &[PublicKey] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
let mut output = String::new();
|
||||
let mut first = true;
|
||||
for pubkey in &self.0 {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
output.push_str(", ");
|
||||
}
|
||||
|
||||
let name = crate::names::display_name_from_pubkey_lookup(pubkey);
|
||||
output.push_str(&name);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn unique_id(&self) -> String {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
for pk in &self.0 {
|
||||
hasher.update(pk.as_bytes());
|
||||
}
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct DmChannelData {
|
||||
pub dm_channel: DmChannel,
|
||||
pub latest_message: Unixtime,
|
||||
pub message_count: usize,
|
||||
pub unread_message_count: usize,
|
||||
}
|
23
src/feed.rs
23
src/feed.rs
@ -1,4 +1,5 @@
|
||||
use crate::comms::{ToMinionMessage, ToMinionPayload, ToMinionPayloadDetail, ToOverlordMessage};
|
||||
use crate::dm_channel::DmChannel;
|
||||
use crate::error::Error;
|
||||
use crate::globals::GLOBALS;
|
||||
use nostr_types::{EventDelegation, EventKind, Id, PublicKey, RelayUrl, Unixtime};
|
||||
@ -18,6 +19,7 @@ pub enum FeedKind {
|
||||
author: Option<PublicKey>,
|
||||
},
|
||||
Person(PublicKey),
|
||||
DmChat(DmChannel),
|
||||
}
|
||||
|
||||
pub struct Feed {
|
||||
@ -28,6 +30,7 @@ pub struct Feed {
|
||||
followed_feed: RwLock<Vec<Id>>,
|
||||
inbox_feed: RwLock<Vec<Id>>,
|
||||
person_feed: RwLock<Vec<Id>>,
|
||||
dm_chat_feed: RwLock<Vec<Id>>,
|
||||
|
||||
// We only recompute the feed at specified intervals (or when they switch)
|
||||
interval_ms: RwLock<u32>,
|
||||
@ -44,6 +47,7 @@ impl Feed {
|
||||
followed_feed: RwLock::new(Vec::new()),
|
||||
inbox_feed: RwLock::new(Vec::new()),
|
||||
person_feed: RwLock::new(Vec::new()),
|
||||
dm_chat_feed: RwLock::new(Vec::new()),
|
||||
interval_ms: RwLock::new(10000), // Every 10 seconds, until we load from settings
|
||||
last_computed: RwLock::new(None),
|
||||
thread_parent: RwLock::new(None),
|
||||
@ -151,6 +155,16 @@ impl Feed {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_feed_to_dmchat(&self, channel: DmChannel) {
|
||||
*self.current_feed_kind.write() = FeedKind::DmChat(channel);
|
||||
*self.thread_parent.write() = None;
|
||||
|
||||
// Recompute as they switch
|
||||
self.sync_recompute();
|
||||
|
||||
self.unlisten();
|
||||
}
|
||||
|
||||
pub fn get_feed_kind(&self) -> FeedKind {
|
||||
self.current_feed_kind.read().to_owned()
|
||||
}
|
||||
@ -170,6 +184,11 @@ impl Feed {
|
||||
self.person_feed.read().clone()
|
||||
}
|
||||
|
||||
pub fn get_dm_chat_feed(&self) -> Vec<Id> {
|
||||
self.sync_maybe_periodic_recompute();
|
||||
self.dm_chat_feed.read().clone()
|
||||
}
|
||||
|
||||
pub fn get_thread_parent(&self) -> Option<Id> {
|
||||
self.sync_maybe_periodic_recompute();
|
||||
*self.thread_parent.read()
|
||||
@ -374,6 +393,10 @@ impl Feed {
|
||||
|
||||
*self.person_feed.write() = events;
|
||||
}
|
||||
FeedKind::DmChat(channel) => {
|
||||
let ids = GLOBALS.storage.dm_events(&channel)?;
|
||||
*self.dm_chat_feed.write() = ids;
|
||||
}
|
||||
}
|
||||
|
||||
self.recompute_lock.store(false, Ordering::Relaxed);
|
||||
|
@ -29,6 +29,7 @@ mod commands;
|
||||
mod comms;
|
||||
mod date_ago;
|
||||
mod delegation;
|
||||
mod dm_channel;
|
||||
mod error;
|
||||
mod feed;
|
||||
mod fetcher;
|
||||
|
@ -367,8 +367,8 @@ impl Minion {
|
||||
ToMinionPayloadDetail::SubscribeMentions => {
|
||||
self.subscribe_mentions(message.job_id).await?;
|
||||
}
|
||||
ToMinionPayloadDetail::SubscribeConfig => {
|
||||
self.subscribe_config(message.job_id).await?;
|
||||
ToMinionPayloadDetail::SubscribeOutbox => {
|
||||
self.subscribe_outbox(message.job_id).await?;
|
||||
}
|
||||
ToMinionPayloadDetail::SubscribeDiscover(pubkeys) => {
|
||||
self.subscribe_discover(message.job_id, pubkeys).await?;
|
||||
@ -473,20 +473,8 @@ impl Minion {
|
||||
}
|
||||
};
|
||||
|
||||
// Allow all feed related event kinds (excluding DMs)
|
||||
let event_kinds = crate::feed::feed_related_event_kinds(false);
|
||||
|
||||
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
||||
// feed related by me
|
||||
// FIXME copy this to listening to my write relays
|
||||
let pkh: PublicKeyHex = pubkey.into();
|
||||
filters.push(Filter {
|
||||
authors: vec![pkh.into()],
|
||||
kinds: event_kinds.clone(),
|
||||
since: Some(feed_since),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
// Allow all feed related event kinds (including DMs)
|
||||
let event_kinds = crate::feed::feed_related_event_kinds(true);
|
||||
|
||||
if !followed_pubkeys.is_empty() {
|
||||
let pkp: Vec<PublicKeyHexPrefix> = followed_pubkeys
|
||||
@ -634,22 +622,43 @@ impl Minion {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Subscribe to the user's config which is on their own write relays
|
||||
async fn subscribe_config(&mut self, job_id: u64) -> Result<(), Error> {
|
||||
// Subscribe to the user's output (config, DMs, etc) which is on their own write relays
|
||||
async fn subscribe_outbox(&mut self, job_id: u64) -> Result<(), Error> {
|
||||
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
||||
let pkh: PublicKeyHex = pubkey.into();
|
||||
|
||||
let filters: Vec<Filter> = vec![Filter {
|
||||
authors: vec![pkh.into()],
|
||||
kinds: vec![
|
||||
EventKind::Metadata,
|
||||
//EventKind::RecommendRelay,
|
||||
EventKind::ContactList,
|
||||
EventKind::RelayList,
|
||||
],
|
||||
// these are all replaceable, no since required
|
||||
..Default::default()
|
||||
}];
|
||||
// Read back in things that we wrote out to our write relays
|
||||
// that we need
|
||||
let filters: Vec<Filter> = vec![
|
||||
// Actual config stuff
|
||||
Filter {
|
||||
authors: vec![pkh.clone().into()],
|
||||
kinds: vec![
|
||||
EventKind::Metadata,
|
||||
//EventKind::RecommendRelay,
|
||||
EventKind::ContactList,
|
||||
EventKind::RelayList,
|
||||
],
|
||||
// these are all replaceable, no since required
|
||||
..Default::default()
|
||||
},
|
||||
// DMs I wrote, all the way back (temp?)
|
||||
Filter {
|
||||
authors: vec![pkh.clone().into()],
|
||||
kinds: vec![EventKind::EncryptedDirectMessage],
|
||||
// all the way back
|
||||
..Default::default()
|
||||
},
|
||||
// GiftWraps I wrote anonymously to myself
|
||||
|
||||
// Posts I wrote, recently
|
||||
Filter {
|
||||
authors: vec![pkh.into()],
|
||||
kinds: crate::feed::feed_related_event_kinds(false), // not DMs
|
||||
// all the way back
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
self.subscribe(filters, "temp_config_feed", job_id).await?;
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ impl Overlord {
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Separately subscribe to our config on our write relays
|
||||
// Separately subscribe to our outbox events on our write relays
|
||||
let write_relay_urls: Vec<RelayUrl> = GLOBALS
|
||||
.storage
|
||||
.filter_relays(|r| r.has_usage_bits(Relay::WRITE))?
|
||||
@ -165,7 +165,7 @@ impl Overlord {
|
||||
reason: RelayConnectionReason::Config,
|
||||
payload: ToMinionPayload {
|
||||
job_id: rand::random::<u64>(),
|
||||
detail: ToMinionPayloadDetail::SubscribeConfig,
|
||||
detail: ToMinionPayloadDetail::SubscribeOutbox,
|
||||
},
|
||||
}],
|
||||
)
|
||||
|
@ -14,6 +14,7 @@ mod import;
|
||||
mod migrations;
|
||||
mod types;
|
||||
|
||||
use crate::dm_channel::{DmChannel, DmChannelData};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::globals::GLOBALS;
|
||||
use crate::people::Person;
|
||||
@ -2293,6 +2294,169 @@ impl Storage {
|
||||
Ok(ranked_relays)
|
||||
}
|
||||
|
||||
/// Get all the DM channels with associated data
|
||||
pub fn dm_channels(&self) -> Result<Vec<DmChannelData>, Error> {
|
||||
let my_pubkey = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let events = self.find_events(
|
||||
&[EventKind::EncryptedDirectMessage, EventKind::GiftWrap],
|
||||
&[],
|
||||
Some(Unixtime(0)),
|
||||
|event| {
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
event.pubkey == my_pubkey || event.is_tagged(&my_pubkey)
|
||||
// Make sure if it has tags, only author and my_pubkey
|
||||
// TBD
|
||||
} else {
|
||||
event.kind == EventKind::GiftWrap
|
||||
}
|
||||
},
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Map from channel to latest-message-time and unread-count
|
||||
let mut map: HashMap<DmChannel, DmChannelData> = HashMap::new();
|
||||
|
||||
for event in &events {
|
||||
let unread = 1 - self.is_event_viewed(event.id)? as usize;
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
let time = event.created_at;
|
||||
let dmchannel = {
|
||||
if event.pubkey != my_pubkey {
|
||||
// DM sent to me
|
||||
DmChannel::new(&[event.pubkey])
|
||||
} else {
|
||||
// DM sent from me
|
||||
let mut maybe_channel: Option<DmChannel> = None;
|
||||
for tag in event.tags.iter() {
|
||||
if let Tag::Pubkey { pubkey, .. } = tag {
|
||||
if let Ok(pk) = PublicKey::try_from(pubkey) {
|
||||
if pk != my_pubkey {
|
||||
maybe_channel = Some(DmChannel::new(&[pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match maybe_channel {
|
||||
Some(dmchannel) => dmchannel,
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
};
|
||||
map.entry(dmchannel.clone())
|
||||
.and_modify(|d| {
|
||||
d.latest_message = d.latest_message.max(time);
|
||||
d.message_count += 1;
|
||||
d.unread_message_count += unread;
|
||||
})
|
||||
.or_insert(DmChannelData {
|
||||
dm_channel: dmchannel,
|
||||
latest_message: time,
|
||||
message_count: 1,
|
||||
unread_message_count: unread,
|
||||
});
|
||||
} else if event.kind == EventKind::GiftWrap {
|
||||
if let Ok(rumor) = GLOBALS.signer.unwrap_giftwrap(event) {
|
||||
let rumor_event = rumor.into_event_with_bad_signature();
|
||||
let time = rumor_event.created_at;
|
||||
let dmchannel = {
|
||||
let mut people: Vec<PublicKey> = rumor_event
|
||||
.people()
|
||||
.iter()
|
||||
.filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok())
|
||||
.filter(|pk| *pk != my_pubkey)
|
||||
.collect();
|
||||
people.push(rumor_event.pubkey); // include author too
|
||||
DmChannel::new(&people)
|
||||
};
|
||||
map.entry(dmchannel.clone())
|
||||
.and_modify(|d| {
|
||||
d.latest_message = d.latest_message.max(time);
|
||||
d.message_count += 1;
|
||||
d.unread_message_count += unread;
|
||||
})
|
||||
.or_insert(DmChannelData {
|
||||
dm_channel: dmchannel,
|
||||
latest_message: time,
|
||||
message_count: 1,
|
||||
unread_message_count: unread,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut output: Vec<DmChannelData> = map.drain().map(|e| e.1).collect();
|
||||
output.sort_by(|a, b| b.latest_message.cmp(&a.latest_message));
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Get DM events (by id) in a channel
|
||||
pub fn dm_events(&self, channel: &DmChannel) -> Result<Vec<Id>, Error> {
|
||||
let my_pubkey = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let mut pass1 = self.find_events(
|
||||
&[EventKind::EncryptedDirectMessage, EventKind::GiftWrap],
|
||||
&[],
|
||||
Some(Unixtime(0)),
|
||||
|event| {
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
if channel.keys().len() > 1 { return false; }
|
||||
if channel.keys().len() == 0 { return true; } // self-channel
|
||||
let other = &channel.keys()[0];
|
||||
let people = event.people();
|
||||
match people.len() {
|
||||
1 => (event.pubkey == my_pubkey && event.is_tagged(other))
|
||||
|| (event.pubkey == *other && event.is_tagged(&my_pubkey)),
|
||||
2 => (event.pubkey == my_pubkey || event.pubkey == *other)
|
||||
&& (event.is_tagged(&my_pubkey) && event.is_tagged(other)),
|
||||
_ => false,
|
||||
}
|
||||
} else if event.kind == EventKind::GiftWrap {
|
||||
// Decrypt in next pass, else we would have to decrypt twice
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut pass2: Vec<Event> = Vec::new();
|
||||
|
||||
for event in pass1.drain(..) {
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
pass2.push(event); // already validated
|
||||
} else if event.kind == EventKind::GiftWrap {
|
||||
if let Ok(rumor) = GLOBALS.signer.unwrap_giftwrap(&event) {
|
||||
let mut rumor_event = rumor.into_event_with_bad_signature();
|
||||
rumor_event.id = event.id; // lie, so it indexes it under the giftwrap
|
||||
let mut tagged: Vec<PublicKey> = rumor_event
|
||||
.people()
|
||||
.drain(..)
|
||||
.filter_map(|(pkh, _, _)| PublicKey::try_from(pkh).ok())
|
||||
.collect();
|
||||
tagged.push(rumor_event.pubkey); // include author
|
||||
tagged.retain(|pk| *pk != my_pubkey); // never include user
|
||||
let this_channel = DmChannel::new(&tagged);
|
||||
if this_channel == *channel {
|
||||
pass2.push(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort
|
||||
pass2.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||
|
||||
Ok(pass2.iter().map(|e| e.id).collect())
|
||||
}
|
||||
|
||||
pub fn rebuild_event_indices(&self) -> Result<(), Error> {
|
||||
let mut wtxn = self.env.write_txn()?;
|
||||
let mut last_key = Id([0; 32]);
|
||||
|
65
src/ui/dm_chat_list.rs
Normal file
65
src/ui/dm_chat_list.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use super::{GossipUi, Page};
|
||||
use crate::dm_channel::DmChannelData;
|
||||
use crate::feed::FeedKind;
|
||||
use crate::globals::GLOBALS;
|
||||
use eframe::egui;
|
||||
use egui::{Context, Label, RichText, ScrollArea, Sense, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
let mut channels: Vec<DmChannelData> = match GLOBALS.storage.dm_channels() {
|
||||
Ok(channels) => channels,
|
||||
Err(_) => {
|
||||
ui.label("ERROR");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
ui.heading("Direct Private Message Channels");
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::vertical()
|
||||
.id_source("dm_chat_list")
|
||||
.max_width(f32::INFINITY)
|
||||
.auto_shrink([false, false])
|
||||
.show(ui, |ui| {
|
||||
let color = app.settings.theme.accent_color();
|
||||
for channeldata in channels.drain(..) {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let channel_name = channeldata.dm_channel.name();
|
||||
ui.label(format!(
|
||||
"({}/{})",
|
||||
channeldata.unread_message_count, channeldata.message_count
|
||||
));
|
||||
|
||||
ui.label(
|
||||
RichText::new(crate::date_ago::date_ago(channeldata.latest_message))
|
||||
.italics()
|
||||
.weak(),
|
||||
)
|
||||
.on_hover_ui(|ui| {
|
||||
if let Ok(stamp) =
|
||||
time::OffsetDateTime::from_unix_timestamp(channeldata.latest_message.0)
|
||||
{
|
||||
if let Ok(formatted) =
|
||||
stamp.format(&time::format_description::well_known::Rfc2822)
|
||||
{
|
||||
ui.label(formatted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ui
|
||||
.add(
|
||||
Label::new(RichText::new(channel_name).color(color))
|
||||
.sense(Sense::click()),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
app.set_page(Page::Feed(FeedKind::DmChat(channeldata.dm_channel)));
|
||||
}
|
||||
});
|
||||
ui.add_space(20.0);
|
||||
}
|
||||
});
|
||||
}
|
@ -145,6 +145,15 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
||||
let feed = GLOBALS.feed.get_person_feed();
|
||||
render_a_feed(app, ctx, frame, ui, feed, false, &pubkey.as_hex_string());
|
||||
}
|
||||
FeedKind::DmChat(channel) => {
|
||||
ui.horizontal(|ui| {
|
||||
recompute_btn(app, ui);
|
||||
});
|
||||
|
||||
let feed = GLOBALS.feed.get_dm_chat_feed();
|
||||
let id = channel.unique_id();
|
||||
render_a_feed(app, ctx, frame, ui, feed, false, &id);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any changes due to changes in which notes are visible
|
||||
|
@ -13,6 +13,7 @@ macro_rules! text_edit_multiline {
|
||||
}
|
||||
|
||||
mod components;
|
||||
mod dm_chat_list;
|
||||
mod feed;
|
||||
mod help;
|
||||
mod people;
|
||||
@ -94,6 +95,7 @@ pub fn run() -> Result<(), Error> {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum Page {
|
||||
DmChatList,
|
||||
Feed(FeedKind),
|
||||
PeopleList,
|
||||
PeopleFollow,
|
||||
@ -114,6 +116,7 @@ enum Page {
|
||||
|
||||
#[derive(Eq, Hash, PartialEq)]
|
||||
enum SubMenu {
|
||||
DmChat,
|
||||
People,
|
||||
Relays,
|
||||
Account,
|
||||
@ -123,6 +126,7 @@ enum SubMenu {
|
||||
impl SubMenu {
|
||||
fn to_id_str(&self) -> &'static str {
|
||||
match self {
|
||||
SubMenu::DmChat => "dmchat_submenu",
|
||||
SubMenu::People => "people_submenu",
|
||||
SubMenu::Account => "account_submenu",
|
||||
SubMenu::Relays => "relays_submenu",
|
||||
@ -148,6 +152,7 @@ enum SettingsTab {
|
||||
impl SubMenuState {
|
||||
fn new() -> Self {
|
||||
let mut submenu_states: HashMap<SubMenu, bool> = HashMap::new();
|
||||
submenu_states.insert(SubMenu::DmChat, false);
|
||||
submenu_states.insert(SubMenu::People, false);
|
||||
submenu_states.insert(SubMenu::Relays, false);
|
||||
submenu_states.insert(SubMenu::Account, false);
|
||||
@ -321,6 +326,7 @@ impl GossipUi {
|
||||
}
|
||||
|
||||
let mut submenu_ids: HashMap<SubMenu, egui::Id> = HashMap::new();
|
||||
submenu_ids.insert(SubMenu::DmChat, egui::Id::new(SubMenu::DmChat.to_id_str()));
|
||||
submenu_ids.insert(SubMenu::People, egui::Id::new(SubMenu::People.to_id_str()));
|
||||
submenu_ids.insert(
|
||||
SubMenu::Account,
|
||||
@ -525,6 +531,9 @@ impl GossipUi {
|
||||
Page::Feed(FeedKind::Person(pubkey)) => {
|
||||
GLOBALS.feed.set_feed_to_person(pubkey.to_owned());
|
||||
}
|
||||
Page::Feed(FeedKind::DmChat(pubkeys)) => {
|
||||
GLOBALS.feed.set_feed_to_dmchat(pubkeys.to_owned());
|
||||
}
|
||||
Page::Search => {
|
||||
self.entering_search_page = true;
|
||||
}
|
||||
@ -658,13 +667,25 @@ impl eframe::App for GossipUi {
|
||||
if self.add_selected_label(ui, matches!(&self.page, Page::Feed(FeedKind::Person(key)) if *key == pubkey), "My Notes").clicked() {
|
||||
self.set_page(Page::Feed(FeedKind::Person(pubkey)));
|
||||
}
|
||||
}
|
||||
if self.add_selected_label(ui, matches!(self.page, Page::Feed(FeedKind::Inbox(_))), "Inbox").clicked() {
|
||||
self.set_page(Page::Feed(FeedKind::Inbox(self.inbox_include_indirect)));
|
||||
if self.add_selected_label(ui, matches!(self.page, Page::Feed(FeedKind::Inbox(_))), "Inbox").clicked() {
|
||||
self.set_page(Page::Feed(FeedKind::Inbox(self.inbox_include_indirect)));
|
||||
}
|
||||
}
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
// ---- DM Chat Submenu ----
|
||||
if GLOBALS.signer.public_key().is_some() {
|
||||
let (mut submenu, header_response) = self.get_openable_menu(ui, SubMenu::DmChat, "DM Chat");
|
||||
submenu.show_body_indented(&header_response, ui, |ui| {
|
||||
self.add_menu_item_page(ui, Page::DmChatList, "List");
|
||||
if let Page::Feed(FeedKind::DmChat(channel)) = &self.page {
|
||||
self.add_menu_item_page(ui, Page::Feed(FeedKind::DmChat(channel.clone())),
|
||||
&channel.name());
|
||||
}
|
||||
});
|
||||
self.after_openable_menu(ui, &submenu);
|
||||
}
|
||||
// ---- People Submenu ----
|
||||
{
|
||||
let (mut submenu, header_response) = self.get_openable_menu(ui, SubMenu::People, "People");
|
||||
@ -870,6 +891,7 @@ impl eframe::App for GossipUi {
|
||||
.show(ctx, |ui| {
|
||||
self.begin_ui(ui);
|
||||
match self.page {
|
||||
Page::DmChatList => dm_chat_list::update(self, ctx, frame, ui),
|
||||
Page::Feed(_) => feed::update(self, ctx, frame, ui),
|
||||
Page::PeopleList | Page::PeopleFollow | Page::PeopleMuted | Page::Person(_) => {
|
||||
people::update(self, ctx, frame, ui)
|
||||
|
Loading…
Reference in New Issue
Block a user