Add support for viewing the inbox (mentions, replies)
This commit is contained in:
parent
b56a8247e1
commit
1ebfa7776f
|
@ -1,4 +1,4 @@
|
||||||
use lemmy_api_common::{person::{GetPersonDetailsResponse, GetPersonDetails}};
|
use lemmy_api_common::{person::{GetPersonDetailsResponse, GetPersonDetails, GetPersonMentionsResponse, GetRepliesResponse, MarkAllAsRead, GetReplies, GetPersonMentions}, lemmy_db_schema::CommentSortType};
|
||||||
|
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
|
|
||||||
|
@ -16,3 +16,32 @@ pub fn get_user(username: String, page: i64) -> std::result::Result<GetPersonDet
|
||||||
pub fn default_person() -> GetPersonDetailsResponse {
|
pub fn default_person() -> GetPersonDetailsResponse {
|
||||||
serde_json::from_str(include_str!("../examples/person.json")).unwrap()
|
serde_json::from_str(include_str!("../examples/person.json")).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mentions(page: i64, unread_only: bool) -> std::result::Result<GetPersonMentionsResponse, reqwest::Error> {
|
||||||
|
let params = GetPersonMentions {
|
||||||
|
auth: settings::get_current_account().jwt.unwrap(),
|
||||||
|
unread_only: Some(unread_only),
|
||||||
|
page: Some(page),
|
||||||
|
sort: Some(CommentSortType::New),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
super::get("/user/mentions", ¶ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_replies(page: i64, unread_only: bool) -> std::result::Result<GetRepliesResponse, reqwest::Error> {
|
||||||
|
let params = GetReplies {
|
||||||
|
auth: settings::get_current_account().jwt.unwrap(),
|
||||||
|
page: Some(page),
|
||||||
|
unread_only: Some(unread_only),
|
||||||
|
sort: Some(CommentSortType::New),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
super::get("/user/replies", ¶ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_all_as_read() -> std::result::Result<GetRepliesResponse, reqwest::Error> {
|
||||||
|
let params = MarkAllAsRead {
|
||||||
|
auth: settings::get_current_account().jwt.unwrap(),
|
||||||
|
};
|
||||||
|
super::post("/user/mark_all_as_read", ¶ms)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
use lemmy_api_common::lemmy_db_views_actor::structs::CommentReplyView;
|
||||||
|
use relm4::{prelude::*, factory::FactoryVecDeque};
|
||||||
|
use gtk::prelude::*;
|
||||||
|
|
||||||
|
use crate::api;
|
||||||
|
|
||||||
|
use super::mention_row::MentionRow;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InboxType {
|
||||||
|
Mentions,
|
||||||
|
Replies,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InboxPage {
|
||||||
|
mentions: FactoryVecDeque<MentionRow>,
|
||||||
|
page: i64,
|
||||||
|
unread_only: bool,
|
||||||
|
type_: InboxType
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InboxInput {
|
||||||
|
UpdateType(InboxType),
|
||||||
|
ToggleUnreadState,
|
||||||
|
FetchInbox,
|
||||||
|
UpdateInbox(Vec<CommentReplyView>)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub)]
|
||||||
|
impl SimpleComponent for InboxPage {
|
||||||
|
type Init = ();
|
||||||
|
type Input = InboxInput;
|
||||||
|
type Output = crate::AppMsg;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
set_margin_all: 15,
|
||||||
|
set_spacing: 10,
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Mentions",
|
||||||
|
connect_clicked => InboxInput::UpdateType(InboxType::Mentions),
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Replies",
|
||||||
|
connect_clicked => InboxInput::UpdateType(InboxType::Replies),
|
||||||
|
},
|
||||||
|
gtk::ToggleButton {
|
||||||
|
set_active: false,
|
||||||
|
set_label: "Show unread only",
|
||||||
|
connect_clicked => InboxInput::ToggleUnreadState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
#[local_ref]
|
||||||
|
mentions -> gtk::Box {
|
||||||
|
set_vexpand: true,
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let mentions = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender());
|
||||||
|
let model = Self { mentions, page: 1, unread_only: false, type_: InboxType::Mentions };
|
||||||
|
let mentions = model.mentions.widget();
|
||||||
|
let widgets = view_output!();
|
||||||
|
sender.input(InboxInput::FetchInbox);
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
InboxInput::FetchInbox => {
|
||||||
|
let type_ = self.type_.clone();
|
||||||
|
let page = self.page.clone();
|
||||||
|
let unread_only = self.unread_only.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let comments = match type_ {
|
||||||
|
InboxType::Mentions => {
|
||||||
|
if let Ok(response) = api::user::get_mentions(page, unread_only) {
|
||||||
|
let serialised = serde_json::to_string(&response.mentions).unwrap();
|
||||||
|
serde_json::from_str(&serialised).ok()
|
||||||
|
} else { None }
|
||||||
|
}
|
||||||
|
InboxType::Replies => {
|
||||||
|
if let Ok(response) = api::user::get_replies(page, unread_only) {
|
||||||
|
Some(response.replies)
|
||||||
|
} else { None }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(comments) = comments {
|
||||||
|
sender.input(InboxInput::UpdateInbox(comments))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InboxInput::UpdateType(type_) => {
|
||||||
|
self.type_ = type_;
|
||||||
|
sender.input(InboxInput::FetchInbox);
|
||||||
|
}
|
||||||
|
InboxInput::ToggleUnreadState => {
|
||||||
|
self.unread_only = !self.unread_only;
|
||||||
|
sender.input(InboxInput::FetchInbox);
|
||||||
|
}
|
||||||
|
InboxInput::UpdateInbox(comments) => {
|
||||||
|
for comment in comments {
|
||||||
|
self.mentions.guard().push_back(comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
use lemmy_api_common::lemmy_db_views_actor::structs::CommentReplyView;
|
||||||
|
use relm4::prelude::*;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4_components::web_image::WebImage;
|
||||||
|
|
||||||
|
use crate::util::get_web_image_url;
|
||||||
|
use crate::util::markdown_to_pango_markup;
|
||||||
|
|
||||||
|
use super::voting_row::VotingRowModel;
|
||||||
|
use super::voting_row::VotingStats;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MentionRow {
|
||||||
|
comment: CommentReplyView,
|
||||||
|
creator_image: Controller<WebImage>,
|
||||||
|
community_image: Controller<WebImage>,
|
||||||
|
voting_row: Controller<VotingRowModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MentionRowMsg {
|
||||||
|
OpenPerson,
|
||||||
|
OpenPost,
|
||||||
|
OpenCommunity,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::factory(pub)]
|
||||||
|
impl FactoryComponent for MentionRow {
|
||||||
|
type Init = CommentReplyView;
|
||||||
|
type Input = MentionRowMsg;
|
||||||
|
type Output = crate::AppMsg;
|
||||||
|
type CommandOutput = ();
|
||||||
|
type Widgets = PostViewWidgets;
|
||||||
|
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,
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
set_label: &self.comment.post.name,
|
||||||
|
add_css_class: "font-bold",
|
||||||
|
set_halign: gtk::Align::Start,
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
gtk::Label {
|
||||||
|
set_label: "in",
|
||||||
|
},
|
||||||
|
if self.comment.community.icon.clone().is_some() {
|
||||||
|
gtk::Box {
|
||||||
|
set_hexpand: false,
|
||||||
|
set_margin_start: 10,
|
||||||
|
set_margin_end: 10,
|
||||||
|
#[local_ref]
|
||||||
|
community_image -> gtk::Box {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gtk::Box {}
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_label: &self.comment.community.title,
|
||||||
|
connect_clicked => MentionRowMsg::OpenCommunity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
set_spacing: 10,
|
||||||
|
set_margin_top: 10,
|
||||||
|
set_vexpand: false,
|
||||||
|
|
||||||
|
if self.comment.creator.avatar.is_some() {
|
||||||
|
gtk::Box {
|
||||||
|
set_hexpand: false,
|
||||||
|
#[local_ref]
|
||||||
|
creator_image -> gtk::Box {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gtk::Box {}
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_label: &self.comment.creator.name,
|
||||||
|
connect_clicked => MentionRowMsg::OpenPerson,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
#[watch]
|
||||||
|
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]
|
||||||
|
voting_row -> gtk::Box {},
|
||||||
|
|
||||||
|
gtk::Separator {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||||
|
Some(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
|
||||||
|
let creator_image = WebImage::builder().launch(get_web_image_url(value.creator.avatar.clone())).detach();
|
||||||
|
let community_image = WebImage::builder().launch(get_web_image_url(value.community.icon.clone())).detach();
|
||||||
|
let voting_row = VotingRowModel::builder().launch(VotingStats::from_comment(value.counts.clone(), value.my_vote)).detach();
|
||||||
|
|
||||||
|
Self { comment: value, creator_image, community_image, voting_row }
|
||||||
|
}
|
||||||
|
|
||||||
|
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 community_image = self.community_image.widget();
|
||||||
|
let voting_row = self.voting_row.widget();
|
||||||
|
let widgets = view_output!();
|
||||||
|
widgets
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||||
|
match message {
|
||||||
|
MentionRowMsg::OpenPerson => {
|
||||||
|
sender.output(crate::AppMsg::OpenPerson(self.comment.creator.name.clone()));
|
||||||
|
}
|
||||||
|
MentionRowMsg::OpenPost => {
|
||||||
|
sender.output(crate::AppMsg::OpenPost(self.comment.post.id.clone()));
|
||||||
|
}
|
||||||
|
MentionRowMsg::OpenCommunity => {
|
||||||
|
sender.output(crate::AppMsg::OpenCommunity(self.comment.community.name.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,3 +5,5 @@ pub mod community_page;
|
||||||
pub mod post_page;
|
pub mod post_page;
|
||||||
pub mod comment_row;
|
pub mod comment_row;
|
||||||
pub mod voting_row;
|
pub mod voting_row;
|
||||||
|
pub mod inbox_page;
|
||||||
|
pub mod mention_row;
|
28
src/main.rs
28
src/main.rs
|
@ -5,7 +5,7 @@ pub mod util;
|
||||||
pub mod dialogs;
|
pub mod dialogs;
|
||||||
|
|
||||||
use api::{user::default_person, community::default_community, post::default_post};
|
use api::{user::default_person, community::default_community, post::default_post};
|
||||||
use components::{post_row::PostRow, community_row::CommunityRow, profile_page::{ProfilePage, self}, community_page::{CommunityPage, self}, post_page::{PostPage, self}};
|
use components::{post_row::PostRow, community_row::CommunityRow, profile_page::{ProfilePage, self}, community_page::{CommunityPage, self}, post_page::{PostPage, self}, inbox_page::InboxPage};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use lemmy_api_common::{lemmy_db_views_actor::structs::CommunityView, lemmy_db_views::structs::PostView, person::GetPersonDetailsResponse, lemmy_db_schema::{newtypes::PostId, ListingType}, post::GetPostResponse, community::GetCommunityResponse};
|
use lemmy_api_common::{lemmy_db_views_actor::structs::CommunityView, lemmy_db_views::structs::PostView, person::GetPersonDetailsResponse, lemmy_db_schema::{newtypes::PostId, ListingType}, post::GetPostResponse, community::GetCommunityResponse};
|
||||||
use relm4::{prelude::*, factory::FactoryVecDeque, set_global_css, actions::{RelmAction, RelmActionGroup}};
|
use relm4::{prelude::*, factory::FactoryVecDeque, set_global_css, actions::{RelmAction, RelmActionGroup}};
|
||||||
|
@ -22,7 +22,8 @@ enum AppState {
|
||||||
Person,
|
Person,
|
||||||
Post,
|
Post,
|
||||||
Login,
|
Login,
|
||||||
Message
|
Message,
|
||||||
|
Inbox
|
||||||
}
|
}
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
|
@ -33,7 +34,8 @@ struct App {
|
||||||
communities: FactoryVecDeque<CommunityRow>,
|
communities: FactoryVecDeque<CommunityRow>,
|
||||||
profile_page: Controller<ProfilePage>,
|
profile_page: Controller<ProfilePage>,
|
||||||
community_page: Controller<CommunityPage>,
|
community_page: Controller<CommunityPage>,
|
||||||
post_page: Controller<PostPage>
|
post_page: Controller<PostPage>,
|
||||||
|
inbox_page: Controller<InboxPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -54,7 +56,8 @@ pub enum AppMsg {
|
||||||
OpenPerson(String),
|
OpenPerson(String),
|
||||||
DoneFetchPerson(GetPersonDetailsResponse),
|
DoneFetchPerson(GetPersonDetailsResponse),
|
||||||
OpenPost(PostId),
|
OpenPost(PostId),
|
||||||
DoneFetchPost(GetPostResponse)
|
DoneFetchPost(GetPostResponse),
|
||||||
|
OpenInbox
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component]
|
#[relm4::component]
|
||||||
|
@ -83,6 +86,10 @@ impl SimpleComponent for App {
|
||||||
set_label: "Subscribed",
|
set_label: "Subscribed",
|
||||||
connect_clicked => AppMsg::StartFetchPosts(Some(ListingType::Subscribed)),
|
connect_clicked => AppMsg::StartFetchPosts(Some(ListingType::Subscribed)),
|
||||||
},
|
},
|
||||||
|
pack_start = >k::Button {
|
||||||
|
set_label: "Inbox",
|
||||||
|
connect_clicked => AppMsg::OpenInbox,
|
||||||
|
},
|
||||||
pack_start = >k::Button {
|
pack_start = >k::Button {
|
||||||
set_label: "Communities",
|
set_label: "Communities",
|
||||||
connect_clicked => AppMsg::ViewCommunities(None),
|
connect_clicked => AppMsg::ViewCommunities(None),
|
||||||
|
@ -247,6 +254,12 @@ impl SimpleComponent for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AppState::Inbox => {
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
#[local_ref]
|
||||||
|
inbox_page -> gtk::Box {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,8 +288,9 @@ impl SimpleComponent for App {
|
||||||
let profile_page = ProfilePage::builder().launch(default_person()).forward(sender.input_sender(), |msg| msg);
|
let profile_page = ProfilePage::builder().launch(default_person()).forward(sender.input_sender(), |msg| msg);
|
||||||
let community_page = CommunityPage::builder().launch(default_community().community_view).forward(sender.input_sender(), |msg| msg);
|
let community_page = CommunityPage::builder().launch(default_community().community_view).forward(sender.input_sender(), |msg| msg);
|
||||||
let post_page = PostPage::builder().launch(default_post()).forward(sender.input_sender(), |msg| msg);
|
let post_page = PostPage::builder().launch(default_post()).forward(sender.input_sender(), |msg| msg);
|
||||||
|
let inbox_page = InboxPage::builder().launch(()).forward(sender.input_sender(), |msg| msg);
|
||||||
|
|
||||||
let model = App { state, posts, communities, profile_page, community_page, post_page, message: None, latest_action: None };
|
let model = App { state, posts, communities, profile_page, community_page, post_page, inbox_page, message: None, latest_action: None };
|
||||||
|
|
||||||
// fetch posts if that's the initial page
|
// fetch posts if that's the initial page
|
||||||
if !instance_url.is_empty() { sender.input(AppMsg::StartFetchPosts(None)) };
|
if !instance_url.is_empty() { sender.input(AppMsg::StartFetchPosts(None)) };
|
||||||
|
@ -287,6 +301,7 @@ impl SimpleComponent for App {
|
||||||
let profile_page = model.profile_page.widget();
|
let profile_page = model.profile_page.widget();
|
||||||
let community_page = model.community_page.widget();
|
let community_page = model.community_page.widget();
|
||||||
let post_page = model.post_page.widget();
|
let post_page = model.post_page.widget();
|
||||||
|
let inbox_page = model.inbox_page.widget();
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
@ -447,6 +462,9 @@ impl SimpleComponent for App {
|
||||||
AppMsg::Retry => {
|
AppMsg::Retry => {
|
||||||
sender.input(self.latest_action.clone().unwrap_or(AppMsg::StartFetchPosts(None)));
|
sender.input(self.latest_action.clone().unwrap_or(AppMsg::StartFetchPosts(None)));
|
||||||
}
|
}
|
||||||
|
AppMsg::OpenInbox => {
|
||||||
|
self.state = AppState::Inbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue