use adw::prelude::*;
use gettextrs::gettext;
use crate::{
i18n::gettext_f,
ngettext_f,
prelude::*,
session::model::{Member, Membership, Room, RoomType},
};
pub async fn confirm_leave_room_dialog(
room: &Room,
parent: &impl IsA<gtk::Widget>,
) -> Option<ConfirmLeaveRoomResponse> {
let (heading, body, response) = if room.category() == RoomType::Invited {
let heading = gettext("Decline Invite?");
let body = if room.join_rule().we_can_join() {
gettext("Do you really want to decline this invite? You can join this room on your own later.")
} else {
gettext(
"Do you really want to decline this invite? You will not be able to join this room without it.",
)
};
let response = gettext("Decline");
(heading, body, response)
} else {
let heading = gettext("Leave Room?");
let body = if room.join_rule().we_can_join() {
gettext("Do you really want to leave this room? You can come back later.")
} else {
gettext(
"Do you really want to leave this room? You will not be able to come back without an invitation.",
)
};
let response = gettext("Leave");
(heading, body, response)
};
let confirm_dialog = adw::AlertDialog::builder()
.default_response("cancel")
.heading(heading)
.body(body)
.build();
confirm_dialog.add_responses(&[("cancel", &gettext("Cancel")), ("leave", &response)]);
confirm_dialog.set_response_appearance("leave", adw::ResponseAppearance::Destructive);
let ignore_inviter_switch = if let Some(inviter) = room
.inviter()
.filter(|_| room.category() == RoomType::Invited)
{
let switch = adw::SwitchRow::builder()
.title(gettext_f(
"Ignore {user}",
&[("user", inviter.user_id().as_str())],
))
.subtitle(gettext(
"All messages or invitations sent by this user will be ignored",
))
.build();
let list_box = gtk::ListBox::builder()
.css_classes(["boxed-list"])
.margin_top(6)
.accessible_role(gtk::AccessibleRole::Group)
.build();
list_box.append(&switch);
confirm_dialog.set_extra_child(Some(&list_box));
Some(switch)
} else {
None
};
if confirm_dialog.choose_future(parent).await == "leave" {
let mut response = ConfirmLeaveRoomResponse::default();
if let Some(switch) = ignore_inviter_switch {
response.ignore_inviter = switch.is_active();
}
Some(response)
} else {
None
}
}
#[derive(Debug, Default, Clone)]
pub struct ConfirmLeaveRoomResponse {
pub ignore_inviter: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RoomMemberDestructiveAction {
Ban(usize),
Kick,
RemoveMessages(usize),
}
pub async fn confirm_room_member_destructive_action_dialog(
member: &Member,
action: RoomMemberDestructiveAction,
parent: &impl IsA<gtk::Widget>,
) -> Option<ConfirmRoomMemberDestructiveActionResponse> {
let (heading, body, response) = match action {
RoomMemberDestructiveAction::Ban(_) => {
let heading = gettext_f("Ban {user}?", &[("user", &member.display_name())]);
let body = gettext_f(
"Are you sure you want to ban {user_id}? They will not be able to join the room again until someone unbans them.",
&[("user_id", member.user_id().as_str())]
);
let response = gettext("Ban");
(heading, body, Some(response))
}
RoomMemberDestructiveAction::Kick => {
let can_rejoin = member.room().join_rule().anyone_can_join();
match member.membership() {
Membership::Invite => {
let heading = gettext_f(
"Revoke Invite for {user}?",
&[("user", &member.display_name())],
);
let body = if can_rejoin {
gettext_f(
"Are you sure you want to revoke the invite for {user_id}? They will still be able to join the room on their own.",
&[("user_id", member.user_id().as_str())]
)
} else {
gettext_f(
"Are you sure you want to revoke the invite for {user_id}? They will not be able to join the room again until someone reinvites them.",
&[("user_id", member.user_id().as_str())]
)
};
let response = gettext("Revoke Invite");
(heading, body, Some(response))
}
Membership::Knock => {
let heading = gettext_f(
"Deny Access to {user}?",
&[("user", &member.display_name())],
);
let body = gettext_f(
"Are you sure you want to deny access to {user_id}?",
&[("user_id", member.user_id().as_str())],
);
let response = gettext("Deny Access");
(heading, body, Some(response))
}
_ => {
let heading = gettext_f("Kick {user}?", &[("user", &member.display_name())]);
let body = if can_rejoin {
gettext_f(
"Are you sure you want to kick {user_id}? They will still be able to join the room again on their own.",
&[("user_id", member.user_id().as_str())]
)
} else {
gettext_f(
"Are you sure you want to kick {user_id}? They will not be able to join the room again until someone invites them.",
&[("user_id", member.user_id().as_str())]
)
};
let response = gettext("Kick");
(heading, body, Some(response))
}
}
}
RoomMemberDestructiveAction::RemoveMessages(count) => {
let n = u32::try_from(count).unwrap_or(u32::MAX);
if count > 0 {
let heading = gettext_f(
"Remove Messages Sent by {user}?",
&[("user", &member.display_name())],
);
let body = ngettext_f(
"This removes all the messages received from the homeserver. Are you sure you want to remove 1 message sent by {user_id}? This cannot be undone.",
"This removes all the messages received from the homeserver. Are you sure you want to remove {n} messages sent by {user_id}? This cannot be undone.",
n,
&[("n", &n.to_string()),("user_id", member.user_id().as_str())]
);
let response = gettext("Remove");
(heading, body, Some(response))
} else {
let heading = gettext_f(
"No Messages Sent by {user}",
&[("user", &member.display_name())],
);
let body = gettext_f(
"There are no messages received from the homeserver sent by {user_id}. You can try to load more by going further back in the room history.",
&[("user_id", member.user_id().as_str())]
);
(heading, body, None)
}
}
};
let child = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.build();
let reason_entry = adw::EntryRow::builder()
.title(gettext("Reason (optional)"))
.build();
let list_box = gtk::ListBox::builder()
.css_classes(["boxed-list"])
.margin_top(6)
.accessible_role(gtk::AccessibleRole::Group)
.build();
list_box.append(&reason_entry);
child.append(&list_box);
let removable_events_count = if let RoomMemberDestructiveAction::Ban(count) = action {
count
} else {
0
};
let remove_events_switch = if removable_events_count > 0 {
let n = u32::try_from(removable_events_count).unwrap_or(u32::MAX);
let switch = adw::SwitchRow::builder()
.title(ngettext_f(
"Remove the latest message sent by the user",
"Remove the {n} latest messages sent by the user",
n,
&[("n", &n.to_string())],
))
.build();
let list_box = gtk::ListBox::builder()
.css_classes(["boxed-list"])
.margin_top(6)
.accessible_role(gtk::AccessibleRole::Group)
.build();
list_box.append(&switch);
child.append(&list_box);
Some(switch)
} else {
None
};
let confirm_dialog = adw::AlertDialog::builder()
.default_response("cancel")
.heading(heading)
.body(body)
.extra_child(&child)
.build();
confirm_dialog.add_responses(&[("cancel", &gettext("Cancel"))]);
if let Some(response) = response {
confirm_dialog.add_responses(&[("confirm", &response)]);
confirm_dialog.set_response_appearance("confirm", adw::ResponseAppearance::Destructive);
}
if confirm_dialog.choose_future(parent).await != "confirm" {
return None;
}
let reason = Some(reason_entry.text().trim().to_owned()).filter(|s| !s.is_empty());
let mut response = ConfirmRoomMemberDestructiveActionResponse {
reason,
..Default::default()
};
if let Some(switch) = remove_events_switch {
response.remove_events = switch.is_active();
}
Some(response)
}
#[derive(Debug, Default, Clone)]
pub struct ConfirmRoomMemberDestructiveActionResponse {
pub reason: Option<String>,
pub remove_events: bool,
}
pub async fn confirm_mute_room_member_dialog(
member: &Member,
parent: &impl IsA<gtk::Widget>,
) -> bool {
let heading = gettext_f(
"Mute {user}?",
&[("user", &member.display_name())],
);
let body = gettext_f(
"Are you sure you want to mute {user_id}? They will not be able to send new messages.",
&[("user_id", member.user_id().as_str())],
);
let confirm_dialog = adw::AlertDialog::builder()
.default_response("cancel")
.heading(heading)
.body(body)
.build();
confirm_dialog.add_responses(&[
("cancel", &gettext("Cancel")),
("mute", &gettext("Mute")),
]);
confirm_dialog.set_response_appearance("mute", adw::ResponseAppearance::Destructive);
confirm_dialog.choose_future(parent).await == "mute"
}
pub async fn confirm_set_room_member_power_level_same_as_own_dialog(
member: &Member,
parent: &impl IsA<gtk::Widget>,
) -> bool {
let heading = gettext_f(
"Promote {user}?",
&[("user", &member.display_name())],
);
let body = gettext_f(
"If you promote {user_id} to the same level as yours, you will not be able to demote them in the future.",
&[("user_id", member.user_id().as_str())],
);
let confirm_dialog = adw::AlertDialog::builder()
.default_response("cancel")
.heading(heading)
.body(body)
.build();
confirm_dialog.add_responses(&[
("cancel", &gettext("Cancel")),
("promote", &gettext("Promote")),
]);
confirm_dialog.set_response_appearance("promote", adw::ResponseAppearance::Destructive);
confirm_dialog.choose_future(parent).await == "promote"
}