feat: add syncing progress
This commit is contained in:
Generated
+16
-22
@@ -2401,15 +2401,6 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fsevent-sys"
|
|
||||||
version = "4.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futf"
|
name = "futf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -5225,20 +5216,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify"
|
name = "notify"
|
||||||
version = "8.2.0"
|
version = "8.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
checksum = "82f812702fbc75d35416d3f884e04925be7e9498f4eceea2e24ba34559284674"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"fsevent-sys",
|
|
||||||
"inotify",
|
"inotify",
|
||||||
"kqueue",
|
"kqueue",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio",
|
||||||
"notify-types",
|
"notify-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-services",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5565,6 +5557,16 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-services"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-text"
|
name = "objc2-core-text"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -9728,15 +9730,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.53.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -10022,6 +10015,7 @@ dependencies = [
|
|||||||
"nostr-connect",
|
"nostr-connect",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"person",
|
"person",
|
||||||
|
"rand 0.10.3",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ impl Global for GlobalChatRegistry {}
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum ChatEvent {
|
pub enum ChatEvent {
|
||||||
Ping,
|
Ping,
|
||||||
|
Loaded,
|
||||||
InboxRelayNotFound,
|
InboxRelayNotFound,
|
||||||
Error(String),
|
Error(String),
|
||||||
}
|
}
|
||||||
@@ -402,6 +403,7 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
if eose && tracking.swap(false, Ordering::AcqRel) {
|
if eose && tracking.swap(false, Ordering::AcqRel) {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
cx.emit(ChatEvent::Loaded);
|
||||||
this.get_rooms(cx);
|
this.get_rooms(cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ impl WebOfTrust {
|
|||||||
.idle_timeout(Duration::from_secs(SYNC_IDLE_TIMEOUT));
|
.idle_timeout(Duration::from_secs(SYNC_IDLE_TIMEOUT));
|
||||||
|
|
||||||
self.syncing = true;
|
self.syncing = true;
|
||||||
|
cx.notify();
|
||||||
|
|
||||||
self.refresh_task = Some(cx.spawn(async move |this, cx| {
|
self.refresh_task = Some(cx.spawn(async move |this, cx| {
|
||||||
// Scope the reconcile to the follow set known so far
|
// Scope the reconcile to the follow set known so far
|
||||||
@@ -265,12 +266,13 @@ impl WebOfTrust {
|
|||||||
|
|
||||||
this.snapshot = Some(snapshot);
|
this.snapshot = Some(snapshot);
|
||||||
cx.emit(WoTEvent);
|
cx.emit(WoTEvent);
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
if grew {
|
if grew {
|
||||||
this.sync(cx);
|
this.sync(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}));
|
}));
|
||||||
@@ -314,6 +316,11 @@ impl WebOfTrust {
|
|||||||
.is_some_and(|snapshot| !snapshot.disabled)
|
.is_some_and(|snapshot| !snapshot.disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a reconciliation with the relays is in flight
|
||||||
|
pub fn is_syncing(&self) -> bool {
|
||||||
|
self.syncing
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the snapshot covers a follow set
|
/// Whether the snapshot covers a follow set
|
||||||
fn has_data(&self) -> bool {
|
fn has_data(&self) -> bool {
|
||||||
self.snapshot
|
self.snapshot
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ nostr-connect.workspace = true
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
|
|||||||
@@ -114,24 +114,25 @@ impl Sidebar {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The bottom navigation, pinned below the list like signed's sidebar.
|
pub(super) fn render_footer(&self, loading: bool, _cx: &mut Context<Self>) -> Div {
|
||||||
pub(super) fn render_footer(&self, _cx: &mut Context<Self>) -> Div {
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_2()
|
.p_2()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.items_start()
|
.items_start()
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.child(
|
.when(!loading, |this| {
|
||||||
Nav::new("sidebar-settings")
|
this.child(
|
||||||
.prefix(Icon::new(IconName::Settings).small())
|
Nav::new("settings")
|
||||||
.center_prefix(true)
|
.prefix(Icon::new(IconName::Settings).small())
|
||||||
.label("Settings")
|
.center_prefix(true)
|
||||||
.text_sm()
|
.label("Settings")
|
||||||
.on_click(|_, window, cx| {
|
.text_sm()
|
||||||
window.dispatch_action(Box::new(Command::ShowSettings), cx)
|
.on_click(|_, window, cx| {
|
||||||
}),
|
window.dispatch_action(Box::new(Command::ShowSettings), cx)
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|||||||
@@ -5,20 +5,22 @@ use chat::{ChatEvent, ChatRegistry};
|
|||||||
use common::DebouncedDelay;
|
use common::DebouncedDelay;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
App, AppContext, Context, Div, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
IntoElement, ObjectFit, ParentElement, Render, SharedString, Styled, StyledImage, Subscription,
|
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Styled,
|
||||||
Task, WeakEntity, Window, div, img, retain_all,
|
StyledImage, Subscription, Task, WeakEntity, Window, div, img, retain_all,
|
||||||
};
|
};
|
||||||
use gpui_kit::base::StyledExt as _;
|
use gpui_kit::base::{Presence, Transition};
|
||||||
use gpui_kit::component::dock::{DockArea, Panel, PanelEvent};
|
use gpui_kit::component::dock::{DockArea, Panel, PanelEvent};
|
||||||
use gpui_kit::component::input::{InputEvent, InputState};
|
use gpui_kit::component::input::{InputEvent, InputState};
|
||||||
use gpui_kit::component::notification::Notification;
|
use gpui_kit::component::notification::Notification;
|
||||||
use gpui_kit::component::spinner::Spinner;
|
use gpui_kit::component::progress::ProgressCircle;
|
||||||
use gpui_kit::component::{ActiveTheme, Sizable, WindowExt, h_flex, v_flex};
|
use gpui_kit::component::{ActiveTheme, WindowExt, h_flex, v_flex};
|
||||||
use instant::Duration;
|
use instant::Duration;
|
||||||
use nip29::{Group, GroupKey, GroupsEvent, GroupsRegistry};
|
use nip29::{Group, GroupKey, GroupsEvent, GroupsRegistry};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{Directory, PersonRegistry};
|
use person::{Directory, PersonRegistry};
|
||||||
|
pub(crate) use rows::open_room;
|
||||||
|
use search::SearchResult;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{FIND_DELAY, NostrRegistry, StateEvent};
|
use state::{FIND_DELAY, NostrRegistry, StateEvent};
|
||||||
use ui::title_bar_drag_handlers;
|
use ui::title_bar_drag_handlers;
|
||||||
@@ -33,8 +35,8 @@ mod onboarding;
|
|||||||
mod rows;
|
mod rows;
|
||||||
mod search;
|
mod search;
|
||||||
|
|
||||||
pub(crate) use rows::open_room;
|
const PROGRESS_TICK_MS: u64 = 500;
|
||||||
use search::SearchResult;
|
const PROGRESS_CAP: f32 = 95.0;
|
||||||
|
|
||||||
pub struct Sidebar {
|
pub struct Sidebar {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
@@ -88,6 +90,11 @@ pub struct Sidebar {
|
|||||||
/// The async global profile search.
|
/// The async global profile search.
|
||||||
global_task: Option<Task<()>>,
|
global_task: Option<Task<()>>,
|
||||||
|
|
||||||
|
/// Simulated gift wrap processing progress, shown while the backlog loads
|
||||||
|
chat_progress: f32,
|
||||||
|
/// Simulated web of trust sync progress
|
||||||
|
sync_progress: f32,
|
||||||
|
|
||||||
tasks: SmallVec<[Task<Result<(), Error>>; 1]>,
|
tasks: SmallVec<[Task<Result<(), Error>>; 1]>,
|
||||||
_subscriptions: SmallVec<[Subscription; 4]>,
|
_subscriptions: SmallVec<[Subscription; 4]>,
|
||||||
}
|
}
|
||||||
@@ -97,7 +104,6 @@ impl Sidebar {
|
|||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let groups = GroupsRegistry::global(cx);
|
let groups = GroupsRegistry::global(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let wot = PersonRegistry::global(cx).read(cx).wot();
|
|
||||||
|
|
||||||
let find_input = cx.new(|cx| {
|
let find_input = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
@@ -105,28 +111,30 @@ impl Sidebar {
|
|||||||
.clean_on_escape()
|
.clean_on_escape()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut tasks = smallvec![];
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
cx.subscribe_in(&chat, window, move |this, _s, event, _window, cx| {
|
cx.subscribe_in(&chat, window, move |this, _, event, _, cx| {
|
||||||
if event == &ChatEvent::Ping {
|
match event {
|
||||||
this.new_requests = true;
|
ChatEvent::Ping => {
|
||||||
cx.notify();
|
this.new_requests = true;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
ChatEvent::Loaded => {
|
||||||
|
this.chat_progress = 0.0;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
subscriptions.push(
|
|
||||||
cx.subscribe_in(&wot, window, |_this, _wot, _event, _window, cx| {
|
|
||||||
cx.notify();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
cx.subscribe_in(&groups, window, |_this, _, event, window, cx| {
|
cx.subscribe_in(&groups, window, |_this, _, event, window, cx| {
|
||||||
match event {
|
match event {
|
||||||
GroupsEvent::Updated => {
|
GroupsEvent::Updated => {
|
||||||
// TODO: find a better way?
|
// TODO: we must not trigger re-render directly like this
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
GroupsEvent::Error(error) => {
|
GroupsEvent::Error(error) => {
|
||||||
@@ -198,6 +206,16 @@ impl Sidebar {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
tasks.push(cx.spawn(async move |this, cx| {
|
||||||
|
let timer = Duration::from_millis(PROGRESS_TICK_MS);
|
||||||
|
loop {
|
||||||
|
cx.background_executor().timer(timer).await;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.tick_progress(cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
dock,
|
dock,
|
||||||
@@ -223,7 +241,9 @@ impl Sidebar {
|
|||||||
global_finding: false,
|
global_finding: false,
|
||||||
global_results: None,
|
global_results: None,
|
||||||
global_task: None,
|
global_task: None,
|
||||||
tasks: smallvec![],
|
chat_progress: 0.0,
|
||||||
|
sync_progress: 0.0,
|
||||||
|
tasks,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,6 +271,80 @@ impl Sidebar {
|
|||||||
self.hide_group(cx);
|
self.hide_group(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Advance the simulated loading progress of any active background work.
|
||||||
|
fn tick_progress(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let logged_in = NostrRegistry::global(cx).read(cx).current_user().is_some();
|
||||||
|
let chat = ChatRegistry::global(cx);
|
||||||
|
let wot = PersonRegistry::global(cx).read(cx).wot();
|
||||||
|
|
||||||
|
let chat_loading = logged_in && chat.read(cx).loading();
|
||||||
|
let syncing = logged_in && wot.read(cx).is_syncing();
|
||||||
|
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
|
if chat_loading {
|
||||||
|
let next = self.advance_progress(self.chat_progress);
|
||||||
|
if next > self.chat_progress {
|
||||||
|
self.chat_progress = next;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else if self.chat_progress != 0.0 {
|
||||||
|
self.chat_progress = 0.0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if syncing {
|
||||||
|
let next = self.advance_progress(self.sync_progress);
|
||||||
|
if next > self.sync_progress {
|
||||||
|
self.sync_progress = next;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else if self.sync_progress != 0.0 {
|
||||||
|
self.sync_progress = 0.0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bump simulated progress by a random step
|
||||||
|
fn advance_progress(&self, progress: f32) -> f32 {
|
||||||
|
(progress + rand::random::<f32>() * 8.0 + 1.0).min(PROGRESS_CAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A floating pill showing simulated progress for a background operation.
|
||||||
|
fn render_loading_pill(
|
||||||
|
&self,
|
||||||
|
id: &'static str,
|
||||||
|
label: &'static str,
|
||||||
|
progress: f32,
|
||||||
|
opacity: f32,
|
||||||
|
cx: &Context<Self>,
|
||||||
|
) -> Div {
|
||||||
|
h_flex()
|
||||||
|
.opacity(opacity)
|
||||||
|
.h_9()
|
||||||
|
.w_full()
|
||||||
|
.px_5()
|
||||||
|
.gap_2p5()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.child(
|
||||||
|
ProgressCircle::new(id)
|
||||||
|
.value(progress)
|
||||||
|
.color(cx.theme().primary)
|
||||||
|
.accessibility_label(label),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().sidebar_foreground)
|
||||||
|
.child(label),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui_kit::component::dock::BasePanel for Sidebar {
|
impl gpui_kit::component::dock::BasePanel for Sidebar {
|
||||||
@@ -290,6 +384,9 @@ impl Render for Sidebar {
|
|||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let loading = chat.read(cx).loading();
|
let loading = chat.read(cx).loading();
|
||||||
|
|
||||||
|
let wot = PersonRegistry::global(cx).read(cx).wot();
|
||||||
|
let syncing = wot.read(cx).is_syncing();
|
||||||
|
|
||||||
let group = self.group.clone().and_then(|group| group.upgrade());
|
let group = self.group.clone().and_then(|group| group.upgrade());
|
||||||
let searching = self.search_open && !self.find_input.read(cx).value().is_empty();
|
let searching = self.search_open && !self.find_input.read(cx).value().is_empty();
|
||||||
let picking_contacts = self.contacts_open && !self.requests_open;
|
let picking_contacts = self.contacts_open && !self.requests_open;
|
||||||
@@ -343,32 +440,46 @@ impl Render for Sidebar {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when(logged_in && !picking_contacts, |this| {
|
.when(logged_in && !picking_contacts, |this| {
|
||||||
this.child(self.render_footer(cx))
|
this.child(self.render_footer(loading || syncing, cx))
|
||||||
})
|
})
|
||||||
.when(loading && logged_in, |this| {
|
.when(logged_in, |this| {
|
||||||
|
let pill_motion = Transition::new(cx.theme().motion_tokens().duration_normal)
|
||||||
|
.easing(cx.theme().motion_tokens().easing_move.clone());
|
||||||
|
|
||||||
|
let chat_presence = Presence::new("chat-presence", loading)
|
||||||
|
.transition(pill_motion.clone())
|
||||||
|
.sample(window, cx);
|
||||||
|
|
||||||
|
let wot_presence = Presence::new("wot-presence", syncing)
|
||||||
|
.transition(pill_motion)
|
||||||
|
.sample(window, cx);
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
v_flex()
|
||||||
.absolute()
|
.absolute()
|
||||||
.bottom_4()
|
.bottom_0()
|
||||||
.left_0()
|
.left_0()
|
||||||
.h_9()
|
|
||||||
.w_full()
|
.w_full()
|
||||||
.px_8()
|
.bg(cx.theme().sidebar)
|
||||||
.child(
|
.text_color(cx.theme().sidebar_foreground)
|
||||||
h_flex()
|
.children(chat_presence.should_render().then(|| {
|
||||||
.gap_2()
|
self.render_loading_pill(
|
||||||
.w_full()
|
"chat-sync",
|
||||||
.h_9()
|
"Syncing messages",
|
||||||
.justify_center()
|
self.chat_progress,
|
||||||
.bg(cx.theme().background.opacity(0.85))
|
chat_presence.progress,
|
||||||
.when(cx.theme().shadow, |this| this.shadow_md())
|
cx,
|
||||||
.rounded_full()
|
)
|
||||||
.text_xs()
|
}))
|
||||||
.font_semibold()
|
.children(wot_presence.should_render().then(|| {
|
||||||
.text_color(cx.theme().muted_foreground)
|
self.render_loading_pill(
|
||||||
.child(Spinner::new().small().color(cx.theme().primary))
|
"wot-sync",
|
||||||
.child("Getting messages..."),
|
"Syncing contacts",
|
||||||
),
|
self.sync_progress,
|
||||||
|
wot_presence.progress,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|||||||
Reference in New Issue
Block a user