feat: resend failed messages (#147)

* .

* .

* fix

* fix

* update

* fix

* .

* .
This commit is contained in:
reya
2025-09-12 17:07:57 +07:00
committed by GitHub
parent 2ea5feaf4b
commit 2ea2519e8b
6 changed files with 261 additions and 157 deletions

View File

@@ -64,18 +64,22 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
}
pub struct ChatSpace {
// Workspace
// App's Title Bar
title_bar: Entity<TitleBar>,
// App's Dock Area
dock: Entity<DockArea>,
// Temporarily store all authentication requests
auth_requests: HashMap<AuthRequest, bool>,
// All authentication requests
auth_requests: HashMap<RelayUrl, AuthRequest>,
// Local state to determine if the user has set up NIP-17 relays
has_nip17_relays: bool,
nip17_relays: bool,
// System
// All subscriptions for observing the app state
_subscriptions: SmallVec<[Subscription; 3]>,
// All long running tasks
_tasks: SmallVec<[Task<()>; 5]>,
}
@@ -182,7 +186,7 @@ impl ChatSpace {
dock,
title_bar,
auth_requests: HashMap::new(),
has_nip17_relays: true,
nip17_relays: true,
_subscriptions: subscriptions,
_tasks: tasks,
}
@@ -573,7 +577,7 @@ impl ChatSpace {
}
Signal::DmRelayNotFound => {
view.update(cx, |this, cx| {
this.set_no_nip17_relays(cx);
this.set_required_relays(cx);
})
.ok();
}
@@ -728,10 +732,8 @@ impl ChatSpace {
match event.created_at >= css.init_at {
// New message: send a signal to notify the UI
true => {
// Prevent notification if the event was sent by Coop
if !css.sent_ids.read().await.contains(&target.id) {
ingester.send(Signal::Message((target.id, event))).await;
}
smol::Timer::after(Duration::from_millis(200)).await;
ingester.send(Signal::Message((target.id, event))).await;
}
// Old message: Coop is probably processing the user's messages during initial load
false => {
@@ -942,20 +944,20 @@ impl ChatSpace {
}
fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) {
for req in self.auth_requests.clone().into_iter() {
self.open_auth_request(req.0, window, cx);
for (_, request) in self.auth_requests.clone().into_iter() {
self.open_auth_request(request, window, cx);
}
}
fn push_auth_request(&mut self, req: &AuthRequest, cx: &mut Context<Self>) {
self.auth_requests.insert(req.to_owned(), false);
self.auth_requests.insert(req.url.clone(), req.to_owned());
cx.notify();
}
fn sending_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
for (req, status) in self.auth_requests.iter_mut() {
for (_, req) in self.auth_requests.iter_mut() {
if req.challenge == challenge {
*status = true;
req.sending = true;
cx.notify();
}
}
@@ -965,16 +967,16 @@ impl ChatSpace {
if let Some(req) = self
.auth_requests
.iter()
.find(|(req, _)| req.challenge == challenge)
.find(|(_, req)| req.challenge == challenge)
{
req.1.to_owned()
req.1.sending
} else {
false
}
}
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
self.auth_requests.retain(|r, _| r.challenge != challenge);
self.auth_requests.retain(|_, r| r.challenge != challenge);
cx.notify();
}
@@ -1026,8 +1028,8 @@ impl ChatSpace {
});
}
fn set_no_nip17_relays(&mut self, cx: &mut Context<Self>) {
self.has_nip17_relays = false;
fn set_required_relays(&mut self, cx: &mut Context<Self>) {
self.nip17_relays = false;
cx.notify();
}
@@ -1054,19 +1056,13 @@ impl ChatSpace {
cx.spawn_in(window, async move |this, cx| {
if let Ok((secret, profile)) = task.await {
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.set_account_layout(secret, profile, window, cx);
})
.ok();
this.update_in(cx, |this, window, cx| {
this.set_account_layout(secret, profile, window, cx);
})
.ok();
} else {
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.set_onboarding_layout(window, cx);
})
.ok();
this.update_in(cx, |this, window, cx| {
this.set_onboarding_layout(window, cx);
})
.ok();
}
@@ -1283,7 +1279,6 @@ impl ChatSpace {
cx: &mut Context<Self>,
) -> impl IntoElement {
let proxy = AppSettings::get_proxy_user_avatars(cx);
let is_auto_auth = AppSettings::read_global(cx).is_auto_auth();
let updating = AutoUpdater::read_global(cx).status.is_updating();
let updated = AutoUpdater::read_global(cx).status.is_updated();
let auth_requests = self.auth_requests.len();
@@ -1322,7 +1317,7 @@ impl ChatSpace {
}),
)
})
.when(auth_requests > 0 && !is_auto_auth, |this| {
.when(auth_requests > 0, |this| {
this.child(
h_flex()
.id("requests")
@@ -1342,7 +1337,7 @@ impl ChatSpace {
})),
)
})
.when(!self.has_nip17_relays, |this| {
.when(!self.nip17_relays, |this| {
this.child(setup_nip17_relay(t!("relays.button")))
})
.child(

View File

@@ -24,7 +24,7 @@ use smallvec::{smallvec, SmallVec};
use smol::fs;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
use ui::emoji_picker::EmojiPicker;
use ui::input::{InputEvent, InputState, TextInput};
@@ -307,6 +307,38 @@ impl Chat {
.detach();
}
fn resend_message(&mut self, id: &EventId, window: &mut Window, cx: &mut Context<Self>) {
if let Some(reports) = self.reports_by_id.get(id).cloned() {
if let Some(message) = self.message(id) {
let backup = AppSettings::get_backup_messages(cx);
let id_clone = id.to_owned();
let message = message.content.to_owned();
let task = self.room.read(cx).resend(reports, message, backup, cx);
cx.spawn_in(window, async move |this, cx| {
match task.await {
Ok(reports) => {
this.update(cx, |this, cx| {
this.reports_by_id.entry(id_clone).and_modify(|this| {
*this = reports;
});
cx.notify();
})
.ok();
}
Err(e) => {
cx.update(|window, cx| {
window.push_notification(e.to_string(), cx);
})
.ok();
}
};
})
.detach();
}
}
}
/// Check if a message failed to send by its ID
fn is_sent_failed(&self, id: &EventId) -> bool {
self.reports_by_id
@@ -609,7 +641,23 @@ impl Chat {
})
.child(text)
.when(is_sent_failed, |this| {
this.child(self.render_message_reports(&id, cx))
this.child(
h_flex()
.gap_1()
.child(self.render_message_reports(&id, cx))
.child(
Button::new(SharedString::from(id.to_hex()))
.label(t!("common.resend"))
.danger()
.xsmall()
.rounded(ButtonRounded::Full)
.on_click(cx.listener(
move |this, _, window, cx| {
this.resend_message(&id, window, cx);
},
)),
),
)
}),
),
)
@@ -677,15 +725,17 @@ impl Chat {
}
fn render_message_sent(&self, id: &EventId, _cx: &Context<Self>) -> impl IntoElement {
div().id("").child(shared_t!("chat.sent")).when_some(
self.sent_reports(id).cloned(),
|this, reports| {
div()
.id(SharedString::from(id.to_hex()))
.child(shared_t!("chat.sent"))
.when_some(self.sent_reports(id).cloned(), |this, reports| {
this.on_click(move |_e, window, cx| {
let reports = reports.clone();
window.open_modal(cx, move |this, _window, cx| {
this.title(shared_t!("chat.reports")).child(
v_flex().pb_4().gap_4().children({
this.show_close(true)
.title(shared_t!("chat.reports"))
.child(v_flex().pb_4().gap_4().children({
let mut items = Vec::with_capacity(reports.len());
for report in reports.iter() {
@@ -693,30 +743,29 @@ impl Chat {
}
items
}),
)
}))
});
})
},
)
})
}
fn render_message_reports(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
h_flex()
.id("")
.gap_1()
.id(SharedString::from(id.to_hex()))
.gap_0p5()
.text_color(cx.theme().danger_foreground)
.text_xs()
.italic()
.child(Icon::new(IconName::Info).small())
.child(Icon::new(IconName::Info).xsmall())
.child(shared_t!("chat.sent_failed"))
.when_some(self.sent_reports(id).cloned(), |this, reports| {
this.on_click(move |_e, window, cx| {
let reports = reports.clone();
window.open_modal(cx, move |this, _window, cx| {
this.title(shared_t!("chat.reports")).child(
v_flex().pb_4().gap_4().children({
this.show_close(true)
.title(shared_t!("chat.reports"))
.child(v_flex().gap_4().pb_4().w_full().children({
let mut items = Vec::with_capacity(reports.len());
for report in reports.iter() {
@@ -724,8 +773,7 @@ impl Chat {
}
items
}),
)
}))
});
})
})
@@ -739,6 +787,7 @@ impl Chat {
v_flex()
.gap_2()
.w_full()
.child(
h_flex()
.gap_2()
@@ -752,7 +801,7 @@ impl Chat {
.child(name.clone()),
),
)
.when(report.nip17_relays_not_found, |this| {
.when(report.relays_not_found, |this| {
this.child(
h_flex()
.flex_wrap()
@@ -773,7 +822,7 @@ impl Chat {
),
)
})
.when_some(report.local_error.clone(), |this, error| {
.when_some(report.error.clone(), |this, error| {
this.child(
h_flex()
.flex_wrap()
@@ -788,38 +837,36 @@ impl Chat {
.child(div().flex_1().w_full().text_center().child(error)),
)
})
.when_some(report.output.clone(), |this, output| {
.when_some(report.status.clone(), |this, output| {
this.child(
v_flex()
.gap_2()
.text_xs()
.w_full()
.children({
let mut items = Vec::with_capacity(output.failed.len());
for (url, msg) in output.failed.into_iter() {
items.push(
h_flex()
.gap_1()
.justify_between()
.text_sm()
v_flex()
.gap_0p5()
.py_1()
.px_2()
.w_full()
.rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background)
.child(
div()
.flex_1()
.py_0p5()
.px_2()
.bg(cx.theme().elevated_surface_background)
.rounded_sm()
.child(url.to_string()),
.text_xs()
.font_semibold()
.line_height(relative(1.25))
.child(SharedString::from(url.to_string())),
)
.child(
div()
.flex_1()
.py_0p5()
.px_2()
.bg(cx.theme().danger_background)
.text_sm()
.text_color(cx.theme().danger_foreground)
.rounded_sm()
.child(msg.to_string()),
.line_height(relative(1.25))
.child(SharedString::from(msg.to_string())),
),
)
}
@@ -831,27 +878,25 @@ impl Chat {
for url in output.success.into_iter() {
items.push(
h_flex()
.gap_1()
.justify_between()
.text_sm()
v_flex()
.gap_0p5()
.py_1()
.px_2()
.w_full()
.rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background)
.child(
div()
.flex_1()
.py_0p5()
.px_2()
.bg(cx.theme().elevated_surface_background)
.rounded_sm()
.child(url.to_string()),
.text_xs()
.font_semibold()
.line_height(relative(1.25))
.child(SharedString::from(url.to_string())),
)
.child(
div()
.flex_1()
.py_0p5()
.px_2()
.bg(cx.theme().secondary_background)
.text_sm()
.text_color(cx.theme().secondary_foreground)
.rounded_sm()
.line_height(relative(1.25))
.child(shared_t!("chat.sent_success")),
),
)
@@ -921,7 +966,7 @@ impl Chat {
let path: SharedString = url.to_string().into();
div()
.id("")
.id(SharedString::from(url.to_string()))
.relative()
.w_16()
.child(