Inbox: support for private messages

This commit is contained in:
Bnyro
2023-06-28 11:42:20 +02:00
parent e0bdc593fb
commit e289867cb8
9 changed files with 304 additions and 35 deletions

View File

@@ -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 {

View File

@@ -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 || {

View File

@@ -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]

View File

@@ -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;

View 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))
}
}
}
}

View File

@@ -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)
}