Skip to content

Commit 837ef1c

Browse files
committed
WIP: Introduce chat subtitle
1 parent 4bdcbdc commit 837ef1c

File tree

8 files changed

+413
-42
lines changed

8 files changed

+413
-42
lines changed

src/session/content/chat_history.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::session::content::ChatHistoryError;
2121
use crate::session::content::ChatHistoryModel;
2222
use crate::session::content::ChatHistoryRow;
2323
use crate::session::content::ChatInfoWindow;
24+
use crate::strings::ChatSubtitleString;
2425
use crate::tdlib::Chat;
2526
use crate::tdlib::ChatType;
2627
use crate::tdlib::SponsoredMessage;
@@ -35,6 +36,7 @@ mod imp {
3536
#[derive(Debug, Default, CompositeTemplate)]
3637
#[template(resource = "/app/drey/paper-plane/ui/content-chat-history.ui")]
3738
pub(crate) struct ChatHistory {
39+
pub binding: RefCell<Option<gtk::ExpressionWatch>>,
3840
pub(super) chat: RefCell<Option<Chat>>,
3941
pub(super) chat_handler: RefCell<Option<glib::SignalHandlerId>>,
4042
pub(super) model: RefCell<Option<ChatHistoryModel>>,
@@ -366,6 +368,17 @@ impl ChatHistory {
366368
imp.list_view.set_model(Some(&selection));
367369

368370
imp.model.replace(Some(model));
371+
if let Some(binding) = imp.binding.take() {
372+
binding.unwatch()
373+
}
374+
375+
// TODO: Make the subtitle label using accent color when user is online or if there is an ongoing chat action
376+
// Bind subtitle
377+
imp.binding.replace(Some(
378+
gtk::ConstantExpression::new(ChatSubtitleString::new(chat.clone(), true))
379+
.chain_property::<ChatSubtitleString>("subtitle")
380+
.bind(&*imp.window_title, "subtitle", Some(self)),
381+
));
369382
}
370383

371384
imp.chat.replace(chat);

src/session/content/chat_info_window.rs

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use adw::prelude::*;
22
use adw::subclass::prelude::AdwWindowImpl;
33
use gettextrs::gettext;
44
use glib::clone;
5-
use glib::closure;
65
use gtk::glib;
76
use gtk::subclass::prelude::*;
87
use gtk::CompositeTemplate;
@@ -14,10 +13,9 @@ use tdlib::types::BasicGroupFullInfo;
1413
use tdlib::types::SupergroupFullInfo;
1514

1615
use crate::expressions;
17-
use crate::i18n::ngettext_f;
18-
use crate::strings;
16+
use crate::strings::ChatSubtitleString;
17+
use crate::strings::UserStatusString;
1918
use crate::tdlib::BasicGroup;
20-
use crate::tdlib::BoxedUserStatus;
2119
use crate::tdlib::Chat;
2220
use crate::tdlib::ChatType;
2321
use crate::tdlib::Supergroup;
@@ -117,9 +115,13 @@ impl ChatInfoWindow {
117115
Some(self),
118116
);
119117

120-
match self.chat().unwrap().type_() {
118+
let chat = self.chat().unwrap();
119+
match chat.type_() {
121120
ChatType::Private(user) => {
122-
self.setup_user_info(user);
121+
// TODO: Add handle own chat;
122+
if !chat.is_own_chat() {
123+
self.setup_user_info(user);
124+
}
123125
}
124126
ChatType::BasicGroup(basic_group) => {
125127
self.setup_basic_group_info(basic_group);
@@ -140,12 +142,9 @@ impl ChatInfoWindow {
140142
if let UserType::Bot(_) = user.type_().0 {
141143
imp.subtitle_label.set_text(Some(&gettext("bot")));
142144
} else {
143-
User::this_expression("status")
144-
.chain_closure::<String>(closure!(
145-
|_: Option<glib::Object>, status: BoxedUserStatus| {
146-
strings::user_status(&status.0)
147-
}
148-
))
145+
let status_string = gtk::ConstantExpression::new(UserStatusString::new(user.clone()));
146+
status_string
147+
.chain_property::<UserStatusString>("string")
149148
.bind(&*imp.subtitle_label, "text", Some(user));
150149
}
151150

@@ -166,24 +165,23 @@ impl ChatInfoWindow {
166165
self.update_info_list_visibility();
167166
}
168167

168+
fn setup_group_member_count(&self) {
169+
let imp = self.imp();
170+
let chat = self.chat().unwrap();
171+
let subtitle_string =
172+
gtk::ConstantExpression::new(ChatSubtitleString::new(chat.clone(), false));
173+
subtitle_string
174+
.chain_property::<ChatSubtitleString>("subtitle")
175+
.bind(&*imp.subtitle_label, "text", Some(chat));
176+
self.update_info_list_visibility();
177+
}
178+
169179
fn setup_basic_group_info(&self, basic_group: &BasicGroup) {
170180
let client_id = self.chat().unwrap().session().client_id();
171181
let basic_group_id = basic_group.id();
172-
let imp = self.imp();
173182

174-
// Members number
175-
BasicGroup::this_expression("member-count")
176-
.chain_closure::<String>(closure!(|_: Option<glib::Object>, member_count: i32| {
177-
ngettext_f(
178-
"{num} member",
179-
"{num} members",
180-
member_count as u32,
181-
&[("num", &member_count.to_string())],
182-
)
183-
}))
184-
.bind(&*imp.subtitle_label, "text", Some(basic_group));
185-
186-
self.update_info_list_visibility();
183+
// Member count
184+
self.setup_group_member_count();
187185

188186
// Full info
189187
spawn(clone!(@weak self as obj => async move {
@@ -217,17 +215,8 @@ impl ChatInfoWindow {
217215
let supergroup_id = supergroup.id();
218216
let imp = self.imp();
219217

220-
// Members number
221-
Supergroup::this_expression("member-count")
222-
.chain_closure::<String>(closure!(|_: Option<glib::Object>, member_count: i32| {
223-
ngettext_f(
224-
"{num} member",
225-
"{num} members",
226-
member_count as u32,
227-
&[("num", &member_count.to_string())],
228-
)
229-
}))
230-
.bind(&*imp.subtitle_label, "text", Some(supergroup));
218+
// Member count
219+
self.setup_group_member_count();
231220

232221
// Link
233222
if !supergroup.username().is_empty() {

src/session/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ impl Session {
237237
Update::ChatUnreadMentionCount(ref data) => {
238238
self.chat(data.chat_id).handle_update(update)
239239
}
240+
Update::ChatOnlineMemberCount(ref data) => {
241+
self.chat(data.chat_id).handle_update(update)
242+
}
240243
Update::ChatIsBlocked(ref data) => self.chat(data.chat_id).handle_update(update),
241244
Update::ChatIsMarkedAsUnread(ref data) => self.chat(data.chat_id).handle_update(update),
242245
Update::DeleteMessages(ref data) => self.chat(data.chat_id).handle_update(update),

src/strings/chat_subtitle_string.rs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use gettextrs::gettext;
2+
use glib::clone;
3+
use gtk::glib;
4+
use gtk::prelude::*;
5+
use gtk::subclass::prelude::*;
6+
7+
use super::UserStatusString;
8+
use crate::i18n::*;
9+
use crate::strings;
10+
use crate::tdlib::Chat;
11+
use crate::tdlib::ChatType;
12+
13+
mod imp {
14+
use std::cell::Cell;
15+
16+
use once_cell::sync::Lazy;
17+
use once_cell::unsync::OnceCell;
18+
19+
use super::*;
20+
21+
#[derive(Debug, Default)]
22+
pub(crate) struct ChatSubtitleString {
23+
pub(super) chat: OnceCell<Chat>,
24+
pub(super) show_actions: Cell<bool>,
25+
pub(super) user_status_string: OnceCell<UserStatusString>,
26+
}
27+
28+
#[glib::object_subclass]
29+
impl ObjectSubclass for ChatSubtitleString {
30+
const NAME: &'static str = "ChatSubtitleString";
31+
type Type = super::ChatSubtitleString;
32+
}
33+
34+
impl ObjectImpl for ChatSubtitleString {
35+
fn properties() -> &'static [glib::ParamSpec] {
36+
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
37+
vec![glib::ParamSpecString::builder("subtitle")
38+
.read_only()
39+
.build()]
40+
});
41+
PROPERTIES.as_ref()
42+
}
43+
44+
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
45+
let obj = self.obj();
46+
47+
match pspec.name() {
48+
"subtitle" => obj.subtitle().to_value(),
49+
_ => unimplemented!(),
50+
}
51+
}
52+
}
53+
}
54+
55+
glib::wrapper! {
56+
pub(crate) struct ChatSubtitleString(ObjectSubclass<imp::ChatSubtitleString>);
57+
}
58+
59+
impl ChatSubtitleString {
60+
pub(crate) fn new(chat: Chat, should_show_actions: bool) -> ChatSubtitleString {
61+
let obj: ChatSubtitleString = glib::Object::builder().build();
62+
63+
if should_show_actions {
64+
chat.actions()
65+
.connect_items_changed(clone!(@weak obj => move |_, _, _, _| {
66+
obj.notify("subtitle");
67+
}));
68+
};
69+
70+
chat.connect_notify_local(
71+
Some("online-member-count"),
72+
clone!(@weak obj => move |_, _| {
73+
obj.notify("subtitle");
74+
}),
75+
);
76+
match chat.type_() {
77+
ChatType::BasicGroup(basic) => {
78+
basic.connect_notify_local(
79+
Some("member-count"),
80+
clone!(@weak obj => move |_, _| {
81+
obj.notify("subtitle");
82+
}),
83+
);
84+
}
85+
ChatType::Supergroup(group) => {
86+
group.connect_notify_local(
87+
Some("member-count"),
88+
clone!(@weak obj => move |_, _| {
89+
obj.notify("subtitle");
90+
}),
91+
);
92+
}
93+
ChatType::Private(user) if !chat.is_own_chat() => {
94+
let user_status_string = UserStatusString::new(user.clone());
95+
user_status_string.connect_notify_local(
96+
Some("string"),
97+
clone!(@weak obj => move |_, _| {
98+
obj.notify("subtitle");
99+
}),
100+
);
101+
obj.imp()
102+
.user_status_string
103+
.set(user_status_string)
104+
.unwrap();
105+
}
106+
ChatType::Secret(secret) => {
107+
let user = secret.user();
108+
let user_status_string = UserStatusString::new(user.clone());
109+
user_status_string.connect_notify_local(
110+
Some("string"),
111+
clone!(@weak obj => move |_, _| {
112+
obj.notify("subtitle");
113+
}),
114+
);
115+
obj.imp()
116+
.user_status_string
117+
.set(user_status_string)
118+
.unwrap();
119+
}
120+
_ => (),
121+
}
122+
123+
obj.imp().show_actions.set(should_show_actions);
124+
obj.imp().chat.set(chat).unwrap();
125+
obj
126+
}
127+
128+
pub(crate) fn subtitle(&self) -> String {
129+
let imp = self.imp();
130+
let chat = imp.chat.get().unwrap();
131+
let should_show_actions = imp.show_actions.get();
132+
133+
if let Some(action) = chat.actions().last() {
134+
should_show_actions
135+
.then(|| strings::chat_action(&action))
136+
.unwrap_or(String::new())
137+
} else {
138+
format!(
139+
"{}{}",
140+
if !chat.is_own_chat() {
141+
match chat.type_() {
142+
ChatType::Private(_) | ChatType::Secret(_) => {
143+
if !chat.is_own_chat() {
144+
imp.user_status_string.get().unwrap().string()
145+
} else {
146+
String::new()
147+
}
148+
}
149+
ChatType::BasicGroup(basic) => {
150+
let m_count = basic.member_count();
151+
match m_count {
152+
0 => gettext("group"),
153+
_ => ngettext_f(
154+
"{num} member",
155+
"{num} members",
156+
m_count as u32,
157+
&[("num", &m_count.to_string())],
158+
),
159+
}
160+
}
161+
ChatType::Supergroup(data) if data.is_channel() => {
162+
let m_count = data.member_count();
163+
match m_count {
164+
0 => gettext("channel"),
165+
_ => ngettext_f(
166+
"{num} subscriber",
167+
"{num} subscribers",
168+
m_count as u32,
169+
&[("num", &m_count.to_string())],
170+
),
171+
}
172+
}
173+
ChatType::Supergroup(data) => {
174+
let m_count = data.member_count();
175+
match m_count {
176+
0 => gettext("group"),
177+
_ => ngettext_f(
178+
"{num} member",
179+
"{num} members",
180+
m_count as u32,
181+
&[("num", &m_count.to_string())],
182+
),
183+
}
184+
}
185+
}
186+
} else {
187+
String::new()
188+
},
189+
if chat.online_member_count() > 1 {
190+
format!(
191+
", {}",
192+
ngettext_f(
193+
"{num} online",
194+
"{num} online",
195+
chat.online_member_count() as u32,
196+
&[("num", &chat.online_member_count().to_string())]
197+
)
198+
)
199+
} else {
200+
String::new()
201+
}
202+
)
203+
}
204+
}
205+
}

src/strings.rs renamed to src/strings/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
mod chat_subtitle_string;
2+
mod user_status_string;
3+
4+
pub(crate) use chat_subtitle_string::ChatSubtitleString;
15
use ellipse::Ellipse;
26
use gettextrs::gettext;
37
use gtk::glib;
@@ -6,6 +10,7 @@ use tdlib::enums::UserStatus;
610
use tdlib::enums::UserType;
711
use tdlib::types::MessageGame;
812
use tdlib::types::MessageGameScore;
13+
pub(crate) use user_status_string::UserStatusString;
914

1015
use crate::i18n::gettext_f;
1116
use crate::i18n::ngettext_f;
@@ -409,7 +414,6 @@ pub(crate) fn user_status(status: &UserStatus) -> String {
409414
let was_online = glib::DateTime::from_unix_local(data.was_online as i64).unwrap();
410415
let time_span = now.difference(&was_online);
411416

412-
// TODO: Add a way to update the string when time passes
413417
if time_span.as_days() > 1 {
414418
// Translators: This is an online status with the date
415419
was_online.format(&gettext("last seen %x")).unwrap().into()

0 commit comments

Comments
 (0)