Inbox: support for private messages
This commit is contained in:
@@ -77,7 +77,6 @@ impl FactoryComponent for CommentRow {
|
||||
set_markup: &markdown_to_pango_markup(self.comment.comment.content.clone()),
|
||||
set_halign: gtk::Align::Start,
|
||||
set_use_markup: true,
|
||||
set_selectable: true,
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use gtk::prelude::*;
|
||||
use lemmy_api_common::lemmy_db_views_actor::structs::CommentReplyView;
|
||||
use lemmy_api_common::{
|
||||
lemmy_db_views::structs::PrivateMessageView, lemmy_db_views_actor::structs::CommentReplyView,
|
||||
};
|
||||
use relm4::{factory::FactoryVecDeque, prelude::*};
|
||||
|
||||
use crate::api;
|
||||
|
||||
use super::mention_row::MentionRow;
|
||||
use super::{mention_row::MentionRow, private_message_row::PrivateMessageRow};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InboxType {
|
||||
Replies,
|
||||
Mentions,
|
||||
PrivateMessages,
|
||||
}
|
||||
|
||||
pub struct InboxPage {
|
||||
mentions: FactoryVecDeque<MentionRow>,
|
||||
private_messages: FactoryVecDeque<PrivateMessageRow>,
|
||||
page: i64,
|
||||
unread_only: bool,
|
||||
type_: InboxType,
|
||||
@@ -25,6 +29,7 @@ pub enum InboxInput {
|
||||
ToggleUnreadState,
|
||||
FetchInbox,
|
||||
UpdateInbox(Vec<CommentReplyView>),
|
||||
UpdatePrivateMessages(Vec<PrivateMessageView>),
|
||||
MarkAllAsRead,
|
||||
}
|
||||
|
||||
@@ -41,32 +46,57 @@ impl SimpleComponent for InboxPage {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_all: 15,
|
||||
set_spacing: 10,
|
||||
gtk::Button {
|
||||
gtk::ToggleButton {
|
||||
set_label: "Replies",
|
||||
connect_clicked => InboxInput::UpdateType(InboxType::Replies),
|
||||
#[watch]
|
||||
set_active: model.type_ == InboxType::Replies,
|
||||
},
|
||||
gtk::Button {
|
||||
gtk::ToggleButton {
|
||||
set_label: "Mentions",
|
||||
connect_clicked => InboxInput::UpdateType(InboxType::Mentions),
|
||||
#[watch]
|
||||
set_active: model.type_ == InboxType::Mentions,
|
||||
},
|
||||
gtk::ToggleButton {
|
||||
set_label: "Private messages",
|
||||
connect_clicked => InboxInput::UpdateType(InboxType::PrivateMessages),
|
||||
#[watch]
|
||||
set_active: model.type_ == InboxType::PrivateMessages,
|
||||
},
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
},
|
||||
gtk::ToggleButton {
|
||||
set_active: false,
|
||||
set_label: "Show unread only",
|
||||
connect_clicked => InboxInput::ToggleUnreadState,
|
||||
},
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
},
|
||||
gtk::Button {
|
||||
set_label: "Mark all as read",
|
||||
connect_clicked => InboxInput::MarkAllAsRead,
|
||||
}
|
||||
},
|
||||
gtk::ScrolledWindow {
|
||||
#[local_ref]
|
||||
mentions -> gtk::Box {
|
||||
set_vexpand: true,
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
match model.type_ {
|
||||
InboxType::PrivateMessages => {
|
||||
gtk::Box {
|
||||
#[local_ref]
|
||||
private_messages -> gtk::Box {
|
||||
set_vexpand: true,
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
gtk::Box {
|
||||
#[local_ref]
|
||||
mentions -> gtk::Box {
|
||||
set_vexpand: true,
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,13 +108,16 @@ impl SimpleComponent for InboxPage {
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let mentions = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender());
|
||||
let private_messages = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender());
|
||||
let model = Self {
|
||||
mentions,
|
||||
private_messages,
|
||||
page: 1,
|
||||
unread_only: false,
|
||||
type_: InboxType::Replies,
|
||||
};
|
||||
let mentions = model.mentions.widget();
|
||||
let private_messages = model.private_messages.widget();
|
||||
let widgets = view_output!();
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
@@ -96,26 +129,40 @@ impl SimpleComponent for InboxPage {
|
||||
let page = self.page.clone();
|
||||
let unread_only = self.unread_only.clone();
|
||||
std::thread::spawn(move || {
|
||||
let comments = match type_ {
|
||||
let message = match type_ {
|
||||
InboxType::Mentions => {
|
||||
if let Ok(response) = api::user::get_mentions(page, unread_only) {
|
||||
// It's just a different object, but its contents are exactly the same
|
||||
let serialised = serde_json::to_string(&response.mentions).unwrap();
|
||||
serde_json::from_str(&serialised).ok()
|
||||
let mentions = serde_json::from_str(&serialised).ok();
|
||||
if let Some(mentions) = mentions {
|
||||
Some(InboxInput::UpdateInbox(mentions))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
InboxType::Replies => {
|
||||
if let Ok(response) = api::user::get_replies(page, unread_only) {
|
||||
Some(response.replies)
|
||||
Some(InboxInput::UpdateInbox(response.replies))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
InboxType::PrivateMessages => {
|
||||
if let Ok(response) =
|
||||
api::private_message::list_private_messages(unread_only, page)
|
||||
{
|
||||
Some(InboxInput::UpdatePrivateMessages(response.private_messages))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(comments) = comments {
|
||||
sender.input(InboxInput::UpdateInbox(comments))
|
||||
if let Some(message) = message {
|
||||
sender.input(message)
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -133,6 +180,12 @@ impl SimpleComponent for InboxPage {
|
||||
self.mentions.guard().push_back(comment);
|
||||
}
|
||||
}
|
||||
InboxInput::UpdatePrivateMessages(messages) => {
|
||||
self.private_messages.guard().clear();
|
||||
for message in messages {
|
||||
self.private_messages.guard().push_back(message);
|
||||
}
|
||||
}
|
||||
InboxInput::MarkAllAsRead => {
|
||||
let show_unread_only = self.unread_only.clone();
|
||||
std::thread::spawn(move || {
|
||||
|
||||
@@ -30,7 +30,7 @@ impl FactoryComponent for MentionRow {
|
||||
type Input = MentionRowMsg;
|
||||
type Output = crate::AppMsg;
|
||||
type CommandOutput = ();
|
||||
type Widgets = PostViewWidgets;
|
||||
type Widgets = MentionRowWidgets;
|
||||
type ParentInput = crate::AppMsg;
|
||||
type ParentWidget = gtk::Box;
|
||||
|
||||
@@ -104,7 +104,6 @@ impl FactoryComponent for MentionRow {
|
||||
set_markup: &markdown_to_pango_markup(self.comment.comment.content.clone()),
|
||||
set_halign: gtk::Align::Start,
|
||||
set_use_markup: true,
|
||||
set_selectable: true,
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
|
||||
@@ -5,5 +5,6 @@ pub mod inbox_page;
|
||||
pub mod mention_row;
|
||||
pub mod post_page;
|
||||
pub mod post_row;
|
||||
pub mod private_message_row;
|
||||
pub mod profile_page;
|
||||
pub mod voting_row;
|
||||
|
||||
108
src/components/private_message_row.rs
Normal file
108
src/components/private_message_row.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use gtk::prelude::*;
|
||||
use lemmy_api_common::lemmy_db_views::structs::PrivateMessageView;
|
||||
use relm4::prelude::FactoryComponent;
|
||||
use relm4::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
|
||||
use crate::util::{self, get_web_image_url, markdown_to_pango_markup};
|
||||
|
||||
pub struct PrivateMessageRow {
|
||||
message: PrivateMessageView,
|
||||
creator_image: Controller<WebImage>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PrivateMessageRowInput {
|
||||
OpenPerson,
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for PrivateMessageRow {
|
||||
type Init = PrivateMessageView;
|
||||
type Input = PrivateMessageRowInput;
|
||||
type Output = crate::AppMsg;
|
||||
type CommandOutput = ();
|
||||
type Widgets = PrivateMessageRowWidgets;
|
||||
type ParentInput = crate::AppMsg;
|
||||
type ParentWidget = gtk::Box;
|
||||
|
||||
view! {
|
||||
root = gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_end: 10,
|
||||
set_margin_start: 10,
|
||||
set_margin_top: 10,
|
||||
set_vexpand: false,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_vexpand: false,
|
||||
set_hexpand: true,
|
||||
|
||||
if self.message.creator.avatar.is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
#[local_ref]
|
||||
creator_image -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &self.message.creator.name,
|
||||
connect_clicked => PrivateMessageRowInput::OpenPerson,
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_margin_start: 10,
|
||||
set_label: &util::format_elapsed_time(self.message.private_message.published)
|
||||
}
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &markdown_to_pango_markup(self.message.private_message.content.clone()),
|
||||
set_halign: gtk::Align::Start,
|
||||
set_use_markup: true,
|
||||
},
|
||||
|
||||
gtk::Separator {}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(init: Self::Init, _index: &Self::Index, _sender: FactorySender<Self>) -> Self {
|
||||
let creator_image = WebImage::builder()
|
||||
.launch(get_web_image_url(init.creator.avatar.clone()))
|
||||
.detach();
|
||||
Self {
|
||||
message: init,
|
||||
creator_image,
|
||||
}
|
||||
}
|
||||
fn init_widgets(
|
||||
&mut self,
|
||||
_index: &Self::Index,
|
||||
root: &Self::Root,
|
||||
_returned_widget: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
|
||||
sender: FactorySender<Self>,
|
||||
) -> Self::Widgets {
|
||||
let creator_image = self.creator_image.widget();
|
||||
let widgets = view_output!();
|
||||
widgets
|
||||
}
|
||||
|
||||
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||
Some(output)
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||
match message {
|
||||
PrivateMessageRowInput::OpenPerson => {
|
||||
sender.output(crate::AppMsg::OpenPerson(self.message.creator.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/util.rs
13
src/util.rs
@@ -10,13 +10,20 @@ pub fn get_web_image_msg(url: Option<DbUrl>) -> WebImageMsg {
|
||||
}
|
||||
|
||||
pub fn get_web_image_url(url: Option<DbUrl>) -> String {
|
||||
return if let Some(url) = url {
|
||||
if let Some(url) = url {
|
||||
url.to_string()
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn markdown_to_pango_markup(text: String) -> String {
|
||||
return html2pango::markup_html(&markdown::to_html(&text)).unwrap_or(text);
|
||||
}
|
||||
|
||||
pub fn format_elapsed_time(time: chrono::NaiveDateTime) -> String {
|
||||
let formatter = timeago::Formatter::new();
|
||||
let current_time = chrono::Utc::now();
|
||||
let published = time.and_utc();
|
||||
formatter.convert_chrono(published, current_time)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user