7DWCVQ7NSWW52VKMR7DM27CMOVDXECTXNHYNLDF4NVAI6YH3HHVQC
NY4FHE2BHD6ZALKMHLI4UR2LRNJJNZ4VWJ7XZFUMVSTTC6XBX2OQC
CEXASC5XWUEA4SWUKX6HSAXV7DSZTIIBXIQEFZAHAFZ7NUIWQLXAC
K772IAVA7N3R2TAC7SAXK4FVUOOROWMKFMUN3HX7A276H6JA6Y7QC
J7PC7UGJE64XZIJXE5YETSHYZUUHSH2B52OA5E5JVQUIF6P72JAQC
MVB4BCD4FIJBLC5ISSG2ZKPYDSEV6IBB3YL75LXVEVZ3BEJDZ52QC
C6ZDDCL46FXQ7W2UEDZ62YJWI5LUEI4ONHQWU3CHWRVTYSLWRDWAC
VO4UOVTJKRL5LOOMKGMQ5DGN5SAZGCEUAUZTKGRE6N3KSCCXHWFAC
}
pub fn rooms_queued_events(
&self,
) -> Vec<(RoomId, Vec<(Uuid, AnySyncRoomEvent, Option<Duration>)>)> {
self.rooms
.iter()
.map(|(id, room)| {
(
id.clone(),
room.queued_events()
.map(|event| {
let txn_id = event.transaction_id().copied().unwrap();
(
txn_id,
event.event_content().clone(),
room.get_wait_for_duration(&txn_id),
)
})
.collect(),
)
})
.collect()
alt_aliases: vec![],
timeline: vec![],
members: Members::new(),
display_name_to_user_id: HashMap::new(),
alt_aliases: Default::default(),
timeline: Default::default(),
members: Default::default(),
display_name_to_user_id: Default::default(),
wait_for_duration: Default::default(),
pub fn ack_event(&mut self, ack_event: &TimelineEvent) {
pub fn wait_for_duration(&mut self, duration: Duration, transaction_id: Uuid) {
*self
.wait_for_duration
.entry(transaction_id)
.or_insert(duration) = duration;
}
pub fn get_wait_for_duration(&self, transaction_id: &Uuid) -> Option<Duration> {
self.wait_for_duration.get(transaction_id).copied()
}
pub fn ack_event(&mut self, transaction_id: &Uuid) {
self.wait_for_duration.remove(transaction_id);
if let Some(thumbnail_image) = {
if is_thumbnail {
Some(content_url.clone())
} else {
timeline_event.thumbnail_url()
}
}
.map(|thumbnail_url| {
thumbnail_store
.get_thumbnail(&thumbnail_url)
.map(|handle| Image::new(handle.clone()).width(Length::Fill))
})
.flatten()
if let Some(thumbnail_image) = thumbnail_store
.get_thumbnail(&content_url)
.or_else(|| {
timeline_event
.thumbnail_url()
.map(|url| thumbnail_store.get_thumbnail(&url))
.flatten()
})
.map(|handle| Image::new(handle.clone()).width(Length::Units(360)))
MessageHistoryScrolled(f32, f32),
/// Sent when the user clicks the logout button.
MessageHistoryScrolled {
prev_scroll_perc: f32,
scroll_perc: f32,
},
/// Sent when the user selects an option from the bottom menu.
/// Sent when a `main::Message::SendMessage` message returns a `LimitExceeded` error.
/// This is used to retry sending a message. The duration is
/// the "retry after" time. The UUID is the transaction ID of
/// this message, used to identify which message to re-send.
/// The room ID is the ID of the room in which the message is
/// stored.
RetrySendMessage {
retry_after: Duration,
transaction_id: Uuid,
room_id: RoomId,
},
if let Some(room_id) = self.current_room_id.as_ref() {
if let Some(room) = rooms.get(room_id) {
let message_composer = TextInput::new(
&mut self.composer_state,
"Enter your message here...",
self.message.as_str(),
Message::MessageChanged,
)
.padding(12)
.size(16)
.style(DarkTextInput)
.on_submit(Message::SendMessageComposer(room_id.clone()));
if let Some((room, room_id)) = self
.current_room_id
.as_ref()
.map(|id| Some((rooms.get(id)?, id)))
.flatten()
{
let message_composer = TextInput::new(
&mut self.composer_state,
"Enter your message here...",
self.message.as_str(),
Message::MessageChanged,
)
.padding(12)
.size(16)
.style(DarkTextInput)
.on_submit(Message::SendMessageComposer(room_id.clone()));
let current_user_id = self.client.current_user_id();
let displayable_event_count = room.displayable_events().count();
let current_user_id = self.client.current_user_id();
let room_disp_len = room.displayable_events().len();
let message_history_list = build_event_history(
&self.thumbnail_store,
room,
¤t_user_id,
self.looking_at_event
.get(room_id)
.copied()
.unwrap_or_else(|| displayable_event_count.saturating_sub(1)),
&mut self.event_history_state,
&mut self.content_open_buts_state,
theme,
);
let message_history_list = build_event_history(
&self.thumbnail_store,
room,
¤t_user_id,
self.looking_at_event
.get(room_id)
.copied()
.unwrap_or_else(|| room_disp_len.saturating_sub(1)),
&mut self.event_history_state,
&mut self.content_open_buts_state,
theme,
);
let mut typing_users_combined = String::new();
let mut typing_members = room.typing_members();
// Remove own user id from the list (if its there)
if let Some(index) = typing_members.iter().position(|id| *id == ¤t_user_id) {
typing_members.remove(index);
}
let typing_members_count = typing_members.len();
let mut typing_users_combined = String::new();
let mut typing_members = room.typing_members();
// Remove own user id from the list (if its there)
if let Some(index) = typing_members.iter().position(|id| *id == ¤t_user_id) {
typing_members.remove(index);
for (index, member_id) in typing_members.iter().enumerate() {
if index > 2 {
typing_users_combined += " and others are typing...";
break;
typing_users_combined += match typing_members_count {
x if x > index + 1 => ", ",
1 => " is typing...",
_ => " are typing...",
};
}
let typing_users = Column::with_children(vec![
Space::with_width(Length::Units(6)).into(),
Row::with_children(vec![
Space::with_width(Length::Units(9)).into(),
Text::new(typing_users_combined).size(14).into(),
])
.into(),
])
.height(Length::Units(14));
let typing_users = Column::with_children(vec![
Space::with_width(Length::Units(6)).into(),
Row::with_children(vec![
Space::with_width(Length::Units(9)).into(),
Text::new(typing_users_combined).size(14).into(),
])
.into(),
]);
let send_file_button =
Button::new(&mut self.send_file_but_state, Text::new("↑").size(28))
.style(DarkButton)
.on_press(Message::SendFile(room_id.clone()));
let send_file_button =
Button::new(&mut self.send_file_but_state, Text::new("↑").size(28))
.style(DarkButton)
.on_press(Message::SendFile(room_id.clone()));
let mut bottom_area_widgets = vec![
send_file_button.into(),
message_composer.width(Length::Fill).into(),
];
let mut bottom_area_widgets = vec![
send_file_button.into(),
message_composer.width(Length::Fill).into(),
];
// This unwrap is safe since we add the room to the map before this
if *self.looking_at_event.get(room_id).unwrap()
< room_disp_len.saturating_sub(SHOWN_MSGS_LIMIT)
{
bottom_area_widgets.push(
Button::new(
&mut self.scroll_to_bottom_but_state,
Text::new("↡").size(28),
)
.style(DarkButton)
.on_press(Message::ScrollToBottom)
.into(),
);
}
let message_area = Column::with_children(vec![
message_history_list,
typing_users.into(),
Container::new(
Row::with_children(bottom_area_widgets)
.spacing(8)
.width(Length::Fill),
// This unwrap is safe since we add the room to the map before this
if *self.looking_at_event.get(room_id).unwrap()
< displayable_event_count.saturating_sub(SHOWN_MSGS_LIMIT)
{
bottom_area_widgets.push(
Button::new(
&mut self.scroll_to_bottom_but_state,
Text::new("↡").size(28),
let message_area = Column::with_children(vec![
message_history_list,
typing_users.into(),
Container::new(
Row::with_children(bottom_area_widgets)
.spacing(8)
.width(Length::Fill),
)
.width(Length::Fill)
.padding(8)
.into(),
]);
screen_widgets.push(
Container::new(message_area)
.width(Length::Fill)
.height(Length::Fill)
.style(BrightContainer)
.into(),
);
fn process_send_message_result(
result: Result<send_message_event::Response, ClientError>,
transaction_id: Uuid,
room_id: RoomId,
) -> super::Message {
use ruma::{api::client::error::ErrorKind as ClientAPIErrorKind, api::error::*};
use ruma_client::Error as InnerClientError;
match result {
Ok(_) => super::Message::Nothing,
Err(err) => {
if let ClientError::Internal(InnerClientError::FromHttpResponse(
FromHttpResponseError::Http(ServerError::Known(err)),
)) = &err
{
if let ClientAPIErrorKind::LimitExceeded {
retry_after_ms: Some(retry_after),
} = err.kind
{
return super::Message::MainScreen(Message::RetrySendMessage {
retry_after,
transaction_id,
room_id,
});
}
}
super::Message::MatrixError(Box::new(err))
}
}
}
Message::OpenContent(content_url, is_thumbnail) => {
let process_path_result = |result| match result {
Ok(path) => {
open::that_in_background(path);
super::Message::Nothing
}
Err(err) => super::Message::MatrixError(Box::new(err)),
};
Message::OpenContent {
content_url,
is_thumbnail,
} => {
let thumbnail_exists = self.thumbnail_store.has_thumbnail(&content_url);
Command::perform(async move { Ok(content_path) }, process_path_result)
Command::perform(
async move {
let thumbnail = if is_thumbnail && !thumbnail_exists {
tokio::fs::read(&content_path)
.await
.map_or(None, |data| Some((data, content_url)))
} else {
None
};
(content_path, thumbnail)
},
|(content_path, thumbnail)| {
open::that_in_background(content_path);
if let Some((data, thumbnail_url)) = thumbnail {
super::Message::MainScreen(Message::DownloadedThumbnail {
thumbnail_url,
thumbnail: ImageHandle::from_memory(data),
})
} else {
super::Message::Nothing
}
},
)
let mut commands = Vec::with_capacity(content.len());
for content in content {
if self.client.has_room(&room_id) {
let inner = self.client.inner();
let transaction_id = Uuid::new_v4();
// This unwrap is safe since we check if the room exists beforehand
// TODO: check if we actually need to check if a room exists beforehand
self.client.get_room_mut(&room_id).unwrap().add_event(
TimelineEvent::new_unacked_message(content.clone(), transaction_id),
);
let content = AnyMessageEventContent::RoomMessage(content);
commands.push(Command::perform(
{
let room_id = room_id.clone();
async move {
(
Client::send_message(
inner,
content,
room_id.clone(),
transaction_id,
)
.await,
transaction_id,
room_id,
)
}
},
|(result, transaction_id, room_id)| {
process_send_message_result(result, transaction_id, room_id)
},
));
if let Some(room) = self.client.get_room_mut(&room_id) {
for content in content {
room.add_event(TimelineEvent::new_unacked_message(content, Uuid::new_v4()));
Message::RetrySendMessage {
retry_after,
transaction_id,
room_id,
} => {
let inner = self.client.inner();
let content = if let Some(Some(Some(content))) =
self.client.get_room(&room_id).map(|room| {
room.timeline()
.iter()
.find(|tevent| tevent.transaction_id() == Some(&transaction_id))
.map(|tevent| tevent.message_content())
}) {
content
} else {
return Command::none();
};
Message::SendMessageResult(errors) => {
use ruma::{api::client::error::ErrorKind as ClientAPIErrorKind, api::error::*};
use ruma_client::Error as InnerClientError;
return Command::perform(
async move {
tokio::time::delay_for(retry_after).await;
(
Client::send_message(inner, content, room_id.clone(), transaction_id)
.await,
transaction_id,
room_id,
)
},
|(result, transaction_id, room_id)| {
process_send_message_result(result, transaction_id, room_id)
},
);
for (room_id, errors) in errors {
for (transaction_id, error) in errors {
if let ClientError::Internal(InnerClientError::FromHttpResponse(
FromHttpResponseError::Http(ServerError::Known(err)),
)) = error
{
if let ClientAPIErrorKind::LimitExceeded { retry_after_ms } = err.kind {
if let Some(retry_after) = retry_after_ms {
if let Some(room) = self.client.get_room_mut(&room_id) {
room.wait_for_duration(retry_after, transaction_id);
}
log::error!("Send message after: {}", retry_after.as_secs());
}
}
} else {
log::error!("Error while sendign message: {}", error);
}
}
}
.map(|(id, room)| (id, room.displayable_events().len()))
.filter(|(id, disp)| {
self.current_room_id.as_ref() != Some(id)
&& if let Some(disp_at) = self.looking_at_event.get(id) {
*disp_at == disp.saturating_sub(1)
} else {
false
.filter_map(|(id, room)| {
let disp = room.displayable_events().count();
if self.current_room_id.as_ref() != Some(id) {
if let Some(disp_at) = self.looking_at_event.get(id) {
if *disp_at == disp.saturating_sub(1) {
return Some((id.clone(), disp));
}
Subscription::from_recipe(SyncRecipe {
client: self.client.inner(),
since,
})
.map(|result| match result {
Ok(response) => {
super::Message::MainScreen(Message::MatrixSyncResponse(Box::from(response)))
}
Err(err) => super::Message::MatrixError(Box::new(err)),
})
} else {
Subscription::none()
sub = Subscription::batch(vec![
sub,
Subscription::from_recipe(SyncRecipe {
client: self.client.inner(),
since,
})
.map(|result| match result {
Ok(response) => {
super::Message::MainScreen(Message::MatrixSyncResponse(Box::from(response)))
}
Err(err) => super::Message::MatrixError(Box::new(err)),
}),
]);
impl<H, I> iced_futures::subscription::Recipe<H, I> for RetrySendEventRecipe
where
H: Hasher,
{
type Output = RetrySendEventResult;
fn hash(&self, state: &mut H) {
std::any::TypeId::of::<Self>().hash(state);
for (id, events) in &self.rooms_queued_events {
id.hash(state);
for (transaction_id, _, retry_after) in events {
transaction_id.hash(state);
retry_after.hash(state);
}
}
}
fn stream(self: Box<Self>, _input: BoxStream<I>) -> BoxStream<Self::Output> {
let future = async move {
let mut room_errors = Vec::new();
for (room_id, events) in self.rooms_queued_events {
let mut transaction_errors = Vec::new();
for (transaction_id, event, retry_after) in events {
if let Some(dur) = retry_after {
tokio::time::delay_for(dur).await;
}
let result = match event {
AnySyncRoomEvent::Message(ev) => {
Client::send_message(
self.client.clone(),
ev.content(),
room_id.clone(),
transaction_id,
)
.await
}
_ => unimplemented!(),
};
if let Err(e) = result {
transaction_errors.push((transaction_id, e));
}
}
room_errors.push((room_id, transaction_errors));
}
room_errors
};
Box::pin(iced_futures::futures::stream::once(future))
}
}