chore: improve room kind handling (#48)
* chore: improve room kind handling * . * add some tooltips * . * fix button hovered style * . * improve prevent duplicate message * .
This commit is contained in:
@@ -1,7 +1,4 @@
|
|||||||
use std::{
|
use std::{cmp::Reverse, collections::BTreeSet};
|
||||||
cmp::Reverse,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
};
|
|
||||||
|
|
||||||
use account::Account;
|
use account::Account;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
@@ -32,7 +29,10 @@ struct GlobalChatRegistry(Entity<ChatRegistry>);
|
|||||||
impl Global for GlobalChatRegistry {}
|
impl Global for GlobalChatRegistry {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NewRoom(pub WeakEntity<Room>);
|
pub enum RoomEmitter {
|
||||||
|
Open(WeakEntity<Room>),
|
||||||
|
Request(RoomKind),
|
||||||
|
}
|
||||||
|
|
||||||
/// Main registry for managing chat rooms and user profiles
|
/// Main registry for managing chat rooms and user profiles
|
||||||
///
|
///
|
||||||
@@ -53,7 +53,7 @@ pub struct ChatRegistry {
|
|||||||
subscriptions: SmallVec<[Subscription; 2]>,
|
subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<NewRoom> for ChatRegistry {}
|
impl EventEmitter<RoomEmitter> for ChatRegistry {}
|
||||||
|
|
||||||
impl ChatRegistry {
|
impl ChatRegistry {
|
||||||
/// Retrieve the Global ChatRegistry instance
|
/// Retrieve the Global ChatRegistry instance
|
||||||
@@ -131,9 +131,10 @@ impl ChatRegistry {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the IDs of all rooms.
|
/// Sort rooms by their created at.
|
||||||
pub fn room_ids(&self, cx: &mut Context<Self>) -> Vec<u64> {
|
pub fn sort(&mut self, cx: &mut Context<Self>) {
|
||||||
self.rooms.iter().map(|room| room.read(cx).id).collect()
|
self.rooms.sort_by_key(|ev| Reverse(ev.read(cx).created_at));
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search rooms by their name.
|
/// Search rooms by their name.
|
||||||
@@ -159,20 +160,15 @@ impl ChatRegistry {
|
|||||||
/// 3. Determines each room's type based on message frequency and trust status
|
/// 3. Determines each room's type based on message frequency and trust status
|
||||||
/// 4. Creates Room entities for each unique room
|
/// 4. Creates Room entities for each unique room
|
||||||
pub fn load_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn load_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// [event] is the Nostr Event
|
|
||||||
// [usize] is the total number of messages, used to determine an ongoing conversation
|
|
||||||
// [bool] is used to determine if the room is trusted
|
|
||||||
type Rooms = Vec<(Event, usize, bool)>;
|
|
||||||
|
|
||||||
// If the user is not logged in, do nothing
|
// If the user is not logged in, do nothing
|
||||||
let Some(user) = Account::get_global(cx).profile_ref() else {
|
let Some(current_user) = Account::get_global(cx).profile_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let public_key = user.public_key();
|
let public_key = current_user.public_key();
|
||||||
|
|
||||||
let task: Task<Result<Rooms, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<BTreeSet<Room>, Error>> = cx.background_spawn(async move {
|
||||||
// Get messages sent by the user
|
// Get messages sent by the user
|
||||||
let send = Filter::new()
|
let send = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
@@ -187,15 +183,24 @@ impl ChatRegistry {
|
|||||||
let recv_events = client.database().query(recv).await?;
|
let recv_events = client.database().query(recv).await?;
|
||||||
let events = send_events.merge(recv_events);
|
let events = send_events.merge(recv_events);
|
||||||
|
|
||||||
let mut room_map: HashMap<u64, (Event, usize, bool)> = HashMap::new();
|
let mut rooms: BTreeSet<Room> = BTreeSet::new();
|
||||||
let mut trusted_keys: HashSet<PublicKey> = HashSet::new();
|
let mut trusted_keys: BTreeSet<PublicKey> = BTreeSet::new();
|
||||||
|
|
||||||
// Process each event and group by room hash
|
// Process each event and group by room hash
|
||||||
for event in events
|
for event in events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.sorted_by_key(|event| Reverse(event.created_at))
|
||||||
.filter(|ev| ev.tags.public_keys().peekable().peek().is_some())
|
.filter(|ev| ev.tags.public_keys().peekable().peek().is_some())
|
||||||
{
|
{
|
||||||
let hash = room_hash(&event);
|
let hash = room_hash(&event);
|
||||||
|
|
||||||
|
if rooms.iter().any(|room| room.id == hash) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut public_keys = event.tags.public_keys().copied().collect_vec();
|
||||||
|
public_keys.push(event.pubkey);
|
||||||
|
|
||||||
let mut is_trust = trusted_keys.contains(&event.pubkey);
|
let mut is_trust = trusted_keys.contains(&event.pubkey);
|
||||||
|
|
||||||
if !is_trust {
|
if !is_trust {
|
||||||
@@ -209,55 +214,50 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
room_map
|
// Check if current_user has sent a message to this room at least once
|
||||||
.entry(hash)
|
let filter = Filter::new()
|
||||||
.and_modify(|(_, count, trusted)| {
|
.kind(Kind::PrivateDirectMessage)
|
||||||
*count += 1;
|
.author(public_key)
|
||||||
*trusted = is_trust;
|
.pubkeys(public_keys);
|
||||||
})
|
// If current user has sent a message at least once, mark as ongoing
|
||||||
.or_insert((event, 1, is_trust));
|
let is_ongoing = client.database().count(filter).await? >= 1;
|
||||||
|
|
||||||
|
if is_ongoing {
|
||||||
|
rooms.insert(Room::new(&event).kind(RoomKind::Ongoing));
|
||||||
|
} else if is_trust {
|
||||||
|
rooms.insert(Room::new(&event).kind(RoomKind::Trusted));
|
||||||
|
} else {
|
||||||
|
rooms.insert(Room::new(&event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort rooms by creation date (newest first)
|
Ok(rooms)
|
||||||
let result: Vec<(Event, usize, bool)> = room_map
|
|
||||||
.into_values()
|
|
||||||
.sorted_by_key(|(ev, _, _)| Reverse(ev.created_at))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Ok(events) = task.await {
|
let rooms = task
|
||||||
|
.await
|
||||||
|
.expect("Failed to load chat rooms. Please restart the application.");
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
let ids = this.room_ids(cx);
|
this.wait_for_eose = false;
|
||||||
let rooms: Vec<Entity<Room>> = events
|
this.rooms.extend(
|
||||||
|
rooms
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(event, count, trusted)| {
|
.sorted_by_key(|room| Reverse(room.created_at))
|
||||||
let hash = room_hash(&event);
|
.filter_map(|room| {
|
||||||
if !ids.iter().any(|this| this == &hash) {
|
if !this.rooms.iter().any(|this| this.read(cx).id == room.id) {
|
||||||
let kind = if count > 2 {
|
Some(cx.new(|_| room))
|
||||||
// If frequency count is greater than 2, mark this room as ongoing
|
|
||||||
RoomKind::Ongoing
|
|
||||||
} else if trusted {
|
|
||||||
RoomKind::Trusted
|
|
||||||
} else {
|
|
||||||
RoomKind::Unknown
|
|
||||||
};
|
|
||||||
Some(cx.new(|_| Room::new(&event).kind(kind)))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect_vec(),
|
||||||
|
);
|
||||||
this.rooms.extend(rooms);
|
|
||||||
this.wait_for_eose = false;
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ impl ChatRegistry {
|
|||||||
weak_room
|
weak_room
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.emit(NewRoom(weak_room));
|
cx.emit(RoomEmitter::Open(weak_room));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a Nostr event into a Coop Message and push it to the belonging room
|
/// Parse a Nostr event into a Coop Message and push it to the belonging room
|
||||||
@@ -289,19 +289,40 @@ impl ChatRegistry {
|
|||||||
/// Updates room ordering based on the most recent messages.
|
/// Updates room ordering based on the most recent messages.
|
||||||
pub fn event_to_message(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn event_to_message(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let id = room_hash(&event);
|
let id = room_hash(&event);
|
||||||
|
let author = event.pubkey;
|
||||||
|
|
||||||
|
let Some(profile) = Account::get_global(cx).profile.to_owned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(room) = self.rooms.iter().find(|room| room.read(cx).id == id) {
|
if let Some(room) = self.rooms.iter().find(|room| room.read(cx).id == id) {
|
||||||
|
// Update room
|
||||||
room.update(cx, |this, cx| {
|
room.update(cx, |this, cx| {
|
||||||
this.created_at(event.created_at, cx);
|
this.created_at(event.created_at, cx);
|
||||||
|
|
||||||
|
// Set this room is ongoing if the new message is from current user
|
||||||
|
if author == profile.public_key() {
|
||||||
|
this.set_ongoing(cx);
|
||||||
|
}
|
||||||
|
|
||||||
// Emit the new message to the room
|
// Emit the new message to the room
|
||||||
cx.defer_in(window, |this, window, cx| {
|
cx.defer_in(window, |this, window, cx| {
|
||||||
this.emit_message(event, window, cx);
|
this.emit_message(event, window, cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-sort the rooms registry by their created at
|
||||||
|
self.sort(cx);
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else {
|
} else {
|
||||||
|
let room = Room::new(&event).kind(RoomKind::Unknown);
|
||||||
|
let kind = room.kind;
|
||||||
|
|
||||||
// Push the new room to the front of the list
|
// Push the new room to the front of the list
|
||||||
self.rooms.insert(0, cx.new(|_| Room::new(&event)));
|
self.rooms.insert(0, cx.new(|_| room));
|
||||||
|
|
||||||
|
cx.emit(RoomEmitter::Request(kind));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,6 +269,18 @@ impl Room {
|
|||||||
self.members.len() > 2
|
self.members.len() > 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the room kind to ongoing
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The context to notify about the update
|
||||||
|
pub fn set_ongoing(&mut self, cx: &mut Context<Self>) {
|
||||||
|
if self.kind != RoomKind::Ongoing {
|
||||||
|
self.kind = RoomKind::Ongoing;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the creation timestamp of the room
|
/// Updates the creation timestamp of the room
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use account::Account;
|
use account::Account;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use chats::ChatRegistry;
|
use chats::{ChatRegistry, RoomEmitter};
|
||||||
use global::{
|
use global::{
|
||||||
constants::{DEFAULT_MODAL_WIDTH, DEFAULT_SIDEBAR_WIDTH},
|
constants::{DEFAULT_MODAL_WIDTH, DEFAULT_SIDEBAR_WIDTH},
|
||||||
get_client,
|
get_client,
|
||||||
@@ -101,13 +101,16 @@ impl ChatSpace {
|
|||||||
&chats,
|
&chats,
|
||||||
window,
|
window,
|
||||||
|this, _state, event, window, cx| {
|
|this, _state, event, window, cx| {
|
||||||
if let Some(room) = event.0.upgrade() {
|
if let RoomEmitter::Open(room) = event {
|
||||||
|
if let Some(room) = room.upgrade() {
|
||||||
this.dock.update(cx, |this, cx| {
|
this.dock.update(cx, |this, cx| {
|
||||||
let panel = chat::init(room, window, cx);
|
let panel = chat::init(room, window, cx);
|
||||||
this.add_panel(panel, DockPlacement::Center, window, cx);
|
this.add_panel(panel, DockPlacement::Center, window, cx);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.push_notification("Failed to open room. Please retry later.", cx);
|
window
|
||||||
|
.push_notification("Failed to open room. Please retry later.", cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
|
use account::Account;
|
||||||
use async_utility::task::spawn;
|
use async_utility::task::spawn;
|
||||||
use chats::{
|
use chats::{
|
||||||
message::Message,
|
message::Message,
|
||||||
room::{Room, SendError},
|
room::{Room, RoomKind, SendError},
|
||||||
};
|
};
|
||||||
use common::{nip96_upload, profile::RenderProfile};
|
use common::{nip96_upload, profile::RenderProfile};
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
@@ -214,10 +215,32 @@ impl Chat {
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: find a better way to prevent duplicate messages during optimistic updates
|
||||||
fn prevent_duplicate_message(&self, new_msg: &Message, cx: &Context<Self>) -> bool {
|
fn prevent_duplicate_message(&self, new_msg: &Message, cx: &Context<Self>) -> bool {
|
||||||
let min_timestamp = new_msg.created_at.as_u64().saturating_sub(2);
|
let Some(current_user) = Account::get_global(cx).profile_ref() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
self.messages.read(cx).iter().any(|existing| {
|
let Some(author) = new_msg.author.as_ref() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if current_user.public_key() != author.public_key() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_timestamp = new_msg.created_at.as_u64().saturating_sub(10);
|
||||||
|
|
||||||
|
self.messages
|
||||||
|
.read(cx)
|
||||||
|
.iter()
|
||||||
|
.filter(|m| {
|
||||||
|
m.borrow()
|
||||||
|
.author
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|p| p.public_key() == current_user.public_key())
|
||||||
|
})
|
||||||
|
.any(|existing| {
|
||||||
let existing = existing.borrow();
|
let existing = existing.borrow();
|
||||||
// Check if messages are within the time window
|
// Check if messages are within the time window
|
||||||
(existing.created_at.as_u64() >= min_timestamp) &&
|
(existing.created_at.as_u64() >= min_timestamp) &&
|
||||||
@@ -263,6 +286,13 @@ impl Chat {
|
|||||||
if let Ok(reports) = send_message.await {
|
if let Ok(reports) = send_message.await {
|
||||||
if !reports.is_empty() {
|
if !reports.is_empty() {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
this.room.update(cx, |this, cx| {
|
||||||
|
if this.kind != RoomKind::Ongoing {
|
||||||
|
this.kind = RoomKind::Ongoing;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.messages.update(cx, |this, cx| {
|
this.messages.update(cx, |this, cx| {
|
||||||
if let Some(msg) = id.and_then(|id| {
|
if let Some(msg) = id.and_then(|id| {
|
||||||
this.iter().find(|msg| msg.borrow().id == Some(id)).cloned()
|
this.iter().find(|msg| msg.borrow().id == Some(id)).cloned()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use account::Account;
|
|||||||
use async_utility::task::spawn;
|
use async_utility::task::spawn;
|
||||||
use chats::{
|
use chats::{
|
||||||
room::{Room, RoomKind},
|
room::{Room, RoomKind},
|
||||||
ChatRegistry,
|
ChatRegistry, RoomEmitter,
|
||||||
};
|
};
|
||||||
|
|
||||||
use common::{debounced_delay::DebouncedDelay, profile::RenderProfile};
|
use common::{debounced_delay::DebouncedDelay, profile::RenderProfile};
|
||||||
@@ -18,6 +18,7 @@ use gpui::{
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{
|
||||||
avatar::Avatar,
|
avatar::Avatar,
|
||||||
button::{Button, ButtonRounded, ButtonVariants},
|
button::{Button, ButtonRounded, ButtonVariants},
|
||||||
@@ -48,13 +49,14 @@ pub struct Sidebar {
|
|||||||
local_result: Entity<Option<Vec<Entity<Room>>>>,
|
local_result: Entity<Option<Vec<Entity<Room>>>>,
|
||||||
global_result: Entity<Option<Vec<Entity<Room>>>>,
|
global_result: Entity<Option<Vec<Entity<Room>>>>,
|
||||||
// Rooms
|
// Rooms
|
||||||
|
indicator: Entity<Option<RoomKind>>,
|
||||||
active_filter: Entity<RoomKind>,
|
active_filter: Entity<RoomKind>,
|
||||||
trusted_only: bool,
|
trusted_only: bool,
|
||||||
// GPUI
|
// GPUI
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
image_cache: Entity<RetainAllImageCache>,
|
image_cache: Entity<RetainAllImageCache>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
subscriptions: SmallVec<[Subscription; 1]>,
|
subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sidebar {
|
impl Sidebar {
|
||||||
@@ -64,14 +66,29 @@ impl Sidebar {
|
|||||||
|
|
||||||
fn view(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn view(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let active_filter = cx.new(|_| RoomKind::Ongoing);
|
let active_filter = cx.new(|_| RoomKind::Ongoing);
|
||||||
|
let indicator = cx.new(|_| None);
|
||||||
let local_result = cx.new(|_| None);
|
let local_result = cx.new(|_| None);
|
||||||
let global_result = cx.new(|_| None);
|
let global_result = cx.new(|_| None);
|
||||||
|
|
||||||
let find_input =
|
let find_input =
|
||||||
cx.new(|cx| InputState::new(window, cx).placeholder("Find or start a conversation"));
|
cx.new(|cx| InputState::new(window, cx).placeholder("Find or start a conversation"));
|
||||||
|
|
||||||
|
let chats = ChatRegistry::global(cx);
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
|
subscriptions.push(cx.subscribe_in(
|
||||||
|
&chats,
|
||||||
|
window,
|
||||||
|
move |this, _chats, event, _window, cx| {
|
||||||
|
if let RoomEmitter::Request(kind) = event {
|
||||||
|
this.indicator.update(cx, |this, cx| {
|
||||||
|
*this = Some(kind.to_owned());
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
subscriptions.push(cx.subscribe_in(
|
subscriptions.push(cx.subscribe_in(
|
||||||
&find_input,
|
&find_input,
|
||||||
window,
|
window,
|
||||||
@@ -103,6 +120,7 @@ impl Sidebar {
|
|||||||
find_debouncer: DebouncedDelay::new(),
|
find_debouncer: DebouncedDelay::new(),
|
||||||
finding: false,
|
finding: false,
|
||||||
trusted_only: false,
|
trusted_only: false,
|
||||||
|
indicator,
|
||||||
active_filter,
|
active_filter,
|
||||||
find_input,
|
find_input,
|
||||||
local_result,
|
local_result,
|
||||||
@@ -275,10 +293,14 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_filter(&mut self, kind: RoomKind, cx: &mut Context<Self>) {
|
fn set_filter(&mut self, kind: RoomKind, cx: &mut Context<Self>) {
|
||||||
|
self.indicator.update(cx, |this, cx| {
|
||||||
|
*this = None;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
self.active_filter.update(cx, |this, cx| {
|
self.active_filter.update(cx, |this, cx| {
|
||||||
*this = kind;
|
*this = kind;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_trusted_only(&mut self, cx: &mut Context<Self>) {
|
fn set_trusted_only(&mut self, cx: &mut Context<Self>) {
|
||||||
@@ -532,6 +554,20 @@ impl Render for Sidebar {
|
|||||||
.child(
|
.child(
|
||||||
Button::new("all")
|
Button::new("all")
|
||||||
.label("All")
|
.label("All")
|
||||||
|
.tooltip("All ongoing conversations")
|
||||||
|
.when_some(
|
||||||
|
self.indicator.read(cx).as_ref(),
|
||||||
|
|this, kind| {
|
||||||
|
this.when(kind == &RoomKind::Ongoing, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.size_1()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().cursor),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
.small()
|
.small()
|
||||||
.bold()
|
.bold()
|
||||||
.secondary()
|
.secondary()
|
||||||
@@ -544,6 +580,20 @@ impl Render for Sidebar {
|
|||||||
.child(
|
.child(
|
||||||
Button::new("requests")
|
Button::new("requests")
|
||||||
.label("Requests")
|
.label("Requests")
|
||||||
|
.tooltip("Incoming new conversations")
|
||||||
|
.when_some(
|
||||||
|
self.indicator.read(cx).as_ref(),
|
||||||
|
|this, kind| {
|
||||||
|
this.when(kind != &RoomKind::Ongoing, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.size_1()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().cursor),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
.small()
|
.small()
|
||||||
.bold()
|
.bold()
|
||||||
.secondary()
|
.secondary()
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ impl RenderOnce for Button {
|
|||||||
this.bg(normal_style.bg)
|
this.bg(normal_style.bg)
|
||||||
.hover(|this| {
|
.hover(|this| {
|
||||||
let hover_style = style.hovered(window, cx);
|
let hover_style = style.hovered(window, cx);
|
||||||
this.bg(hover_style.bg)
|
this.bg(hover_style.bg).text_color(hover_style.fg)
|
||||||
})
|
})
|
||||||
.active(|this| {
|
.active(|this| {
|
||||||
let active_style = style.active(window, cx);
|
let active_style = style.active(window, cx);
|
||||||
|
|||||||
Reference in New Issue
Block a user