mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Allow choosing allowed reactions in groups / channels.
This commit is contained in:
parent
bfdbb64295
commit
9c18f7b0e3
13 changed files with 363 additions and 12 deletions
|
@ -180,6 +180,8 @@ PRIVATE
|
|||
boxes/peers/edit_peer_history_visibility_box.h
|
||||
boxes/peers/edit_peer_permissions_box.cpp
|
||||
boxes/peers/edit_peer_permissions_box.h
|
||||
boxes/peers/edit_peer_reactions.cpp
|
||||
boxes/peers/edit_peer_reactions.h
|
||||
boxes/peers/edit_peer_requests_box.cpp
|
||||
boxes/peers/edit_peer_requests_box.h
|
||||
boxes/peers/edit_peer_type_box.cpp
|
||||
|
|
|
@ -1005,9 +1005,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_manage_peer_removed_users" = "Removed users";
|
||||
"lng_manage_peer_permissions" = "Permissions";
|
||||
"lng_manage_peer_invite_links" = "Invite links";
|
||||
"lng_manage_peer_reactions" = "Reactions";
|
||||
"lng_manage_peer_reactions_off" = "Off";
|
||||
"lng_manage_peer_requests" = "Member Requests";
|
||||
"lng_manage_peer_requests_channel" = "Subscriber Requests";
|
||||
|
||||
"lng_manage_peer_reactions_enable" = "Enable Reactions";
|
||||
"lng_manage_peer_reactions_about" = "Allow members to react to group messages.";
|
||||
"lng_manage_peer_reactions_about_channel" = "Allow subscribers to react to channel posts.";
|
||||
"lng_manage_peer_reactions_available" = "Available reactions";
|
||||
|
||||
"lng_manage_peer_group_type" = "Group type";
|
||||
"lng_manage_peer_channel_type" = "Channel type";
|
||||
"lng_manage_peer_link_type" = "Link type";
|
||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
|
@ -31,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -288,7 +290,8 @@ private:
|
|||
object_ptr<Ui::RpWidget> createManageGroupButtons();
|
||||
object_ptr<Ui::RpWidget> createStickersEdit();
|
||||
|
||||
bool canEditInformation() const;
|
||||
[[nodiscard]] bool canEditInformation() const;
|
||||
[[nodiscard]] bool canEditReactions() const;
|
||||
void refreshHistoryVisibility();
|
||||
void showEditPeerTypeBox(
|
||||
std::optional<rpl::producer<QString>> error = {});
|
||||
|
@ -596,6 +599,17 @@ bool Controller::canEditInformation() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Controller::canEditReactions() const {
|
||||
if (const auto channel = _peer->asChannel()) {
|
||||
return channel->amCreator()
|
||||
|| (channel->adminRights() & ChatAdminRight::ChangeInfo);
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->amCreator()
|
||||
|| (chat->adminRights() & ChatAdminRight::ChangeInfo);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Controller::refreshHistoryVisibility() {
|
||||
if (!_controls.historyVisibilityWrap) {
|
||||
return;
|
||||
|
@ -1017,6 +1031,39 @@ void Controller::fillManageSection() {
|
|||
}, wrap->lifetime());
|
||||
}
|
||||
}
|
||||
if (canEditReactions()) {
|
||||
const auto session = &_peer->session();
|
||||
auto reactionsCount = Info::Profile::MigratedOrMeValue(
|
||||
_peer
|
||||
) | rpl::map(
|
||||
Info::Profile::AllowedReactionsCountValue
|
||||
) | rpl::flatten_latest();
|
||||
auto fullCount = Info::Profile::FullReactionsCountValue(session);
|
||||
auto label = rpl::combine(
|
||||
std::move(reactionsCount),
|
||||
std::move(fullCount)
|
||||
) | rpl::map([=](int allowed, int total) {
|
||||
return allowed
|
||||
? QString::number(allowed) + " / " + QString::number(total)
|
||||
: tr::lng_manage_peer_reactions_off(tr::now);
|
||||
});
|
||||
const auto done = [=](const std::vector<QString> &chosen) {
|
||||
SaveAllowedReactions(_peer, chosen);
|
||||
};
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_reactions(),
|
||||
std::move(label),
|
||||
[=] {
|
||||
_navigation->parentController()->show(Box(
|
||||
EditAllowedReactionsBox,
|
||||
!_peer->isBroadcast(),
|
||||
session->data().reactions().list(),
|
||||
session->data().reactions().list(_peer),
|
||||
done));
|
||||
},
|
||||
st::infoIconReactions);
|
||||
}
|
||||
if (canViewAdmins) {
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
|
|
209
Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
Normal file
209
Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using Data::Reaction;
|
||||
|
||||
void AddReactionIcon(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
not_null<DocumentData*> document) {
|
||||
struct State {
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
QImage image;
|
||||
};
|
||||
|
||||
const auto size = st::editPeerReactionsPreview;
|
||||
const auto state = button->lifetime().make_state<State>(State{
|
||||
.media = document->createMediaView(),
|
||||
});
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button.get());
|
||||
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
icon->resize(size, size);
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
icon->moveToLeft(
|
||||
st::settingsSectionIconLeft,
|
||||
(size.height() - icon->height()) / 2,
|
||||
size.width());
|
||||
}, icon->lifetime());
|
||||
|
||||
const auto setImage = [=](not_null<Image*> image) {
|
||||
state->image = Images::prepare(
|
||||
image->original(),
|
||||
size * style::DevicePixelRatio(),
|
||||
size * style::DevicePixelRatio(),
|
||||
Images::Option::Smooth | Images::Option::TransparentBackground,
|
||||
size,
|
||||
size);
|
||||
icon->update();
|
||||
};
|
||||
if (const auto image = state->media->getStickerLarge()) {
|
||||
setImage(image);
|
||||
} else {
|
||||
document->session().downloaderTaskFinished(
|
||||
) | rpl::map([=] {
|
||||
return state->media->getStickerLarge();
|
||||
}) | rpl::filter([=](Image *image) {
|
||||
return (image != nullptr);
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](not_null<Image*> image) {
|
||||
setImage(image);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
Painter p(icon);
|
||||
const auto width = icon->width();
|
||||
if (!state->image.isNull()) {
|
||||
p.drawImage(0, 0, state->image);
|
||||
}
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EditAllowedReactionsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
const std::vector<Reaction> &list,
|
||||
const std::vector<Reaction> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback) {
|
||||
box->setTitle(tr::lng_manage_peer_reactions());
|
||||
|
||||
struct State {
|
||||
base::flat_map<QString, not_null<Ui::SettingsButton*>> toggles;
|
||||
rpl::variable<bool> anyToggled;
|
||||
rpl::event_stream<bool> forceToggleAll;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.anyToggled = !selected.empty(),
|
||||
});
|
||||
|
||||
const auto collect = [=] {
|
||||
auto result = std::vector<QString>();
|
||||
result.reserve(state->toggles.size());
|
||||
for (const auto &[emoji, button] : state->toggles) {
|
||||
if (button->toggled()) {
|
||||
result.push_back(emoji);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto enabled = Settings::AddButton(
|
||||
container,
|
||||
tr::lng_manage_peer_reactions_enable(),
|
||||
st::manageGroupButton.button);
|
||||
Ui::CreateChild<Info::Profile::FloatingIcon>(
|
||||
enabled.get(),
|
||||
st::infoIconReactions,
|
||||
st::manageGroupButton.iconPosition);
|
||||
enabled->toggleOn(state->anyToggled.value());
|
||||
enabled->toggledChanges(
|
||||
) | rpl::filter([=](bool value) {
|
||||
return (value != state->anyToggled.current());
|
||||
}) | rpl::start_to_stream(state->forceToggleAll, enabled->lifetime());
|
||||
|
||||
Settings::AddSkip(container);
|
||||
Settings::AddDividerText(
|
||||
container,
|
||||
(isGroup
|
||||
? tr::lng_manage_peer_reactions_about
|
||||
: tr::lng_manage_peer_reactions_about_channel)());
|
||||
|
||||
Settings::AddSkip(container);
|
||||
Settings::AddSubsectionTitle(
|
||||
container,
|
||||
tr::lng_manage_peer_reactions_available());
|
||||
|
||||
const auto active = [&](const Data::Reaction &entry) {
|
||||
return ranges::contains(selected, entry.emoji, &Reaction::emoji);
|
||||
};
|
||||
const auto add = [&](const Data::Reaction &entry) {
|
||||
const auto button = Settings::AddButton(
|
||||
container,
|
||||
rpl::single(entry.title),
|
||||
st::manageGroupButton.button);
|
||||
AddReactionIcon(button, entry.staticIcon);
|
||||
state->toggles.emplace(entry.emoji, button);
|
||||
button->toggleOn(rpl::single(
|
||||
active(entry)
|
||||
) | rpl::then(
|
||||
state->forceToggleAll.events()
|
||||
));
|
||||
button->toggledChanges(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
if (toggled) {
|
||||
state->anyToggled = true;
|
||||
} else if (collect().empty()) {
|
||||
state->anyToggled = false;
|
||||
}
|
||||
}, button->lifetime());
|
||||
};
|
||||
for (const auto &entry : list) {
|
||||
add(entry);
|
||||
}
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto ids = collect();
|
||||
box->closeBox();
|
||||
callback(ids);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
void SaveAllowedReactions(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<QString> &allowed) {
|
||||
auto ids = allowed | ranges::views::transform([=](QString value) {
|
||||
return MTP_string(value);
|
||||
}) | ranges::to<QVector>;
|
||||
|
||||
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
|
||||
peer->input,
|
||||
MTP_vector<MTPstring>(ids)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setAllowedReactions(allowed);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setAllowedReactions(allowed);
|
||||
} else {
|
||||
Unexpected("Invalid peer type in SaveAllowedReactions.");
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == qstr("REACTION_INVALID")) {
|
||||
peer->updateFullForced();
|
||||
peer->owner().reactions().refresh();
|
||||
}
|
||||
}).send();
|
||||
}
|
27
Telegram/SourceFiles/boxes/peers/edit_peer_reactions.h
Normal file
27
Telegram/SourceFiles/boxes/peers/edit_peer_reactions.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Data {
|
||||
struct Reaction;
|
||||
} // namespace Data
|
||||
|
||||
void EditAllowedReactionsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
const std::vector<Data::Reaction> &list,
|
||||
const std::vector<Data::Reaction> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback);
|
||||
|
||||
void SaveAllowedReactions(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<QString> &allowed);
|
|
@ -86,17 +86,18 @@ struct PeerUpdate {
|
|||
BannedUsers = (1ULL << 25),
|
||||
Rights = (1ULL << 26),
|
||||
PendingRequests = (1ULL << 27),
|
||||
Reactions = (1ULL << 28),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 28),
|
||||
StickersSet = (1ULL << 29),
|
||||
ChannelLinkedChat = (1ULL << 30),
|
||||
ChannelLocation = (1ULL << 31),
|
||||
Slowmode = (1ULL << 32),
|
||||
GroupCall = (1ULL << 33),
|
||||
ChannelAmIn = (1ULL << 29),
|
||||
StickersSet = (1ULL << 30),
|
||||
ChannelLinkedChat = (1ULL << 31),
|
||||
ChannelLocation = (1ULL << 32),
|
||||
Slowmode = (1ULL << 33),
|
||||
GroupCall = (1ULL << 34),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 33),
|
||||
LastUsedBit = (1ULL << 34),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -762,7 +762,10 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
|
|||
}
|
||||
|
||||
void ChannelData::setAllowedReactions(std::vector<QString> list) {
|
||||
_allowedReactions = std::move(list);
|
||||
if (_allowedReactions != list) {
|
||||
_allowedReactions = std::move(list);
|
||||
session().changes().peerUpdated(this, UpdateFlag::Reactions);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<QString> &ChannelData::allowedReactions() const {
|
||||
|
|
|
@ -288,7 +288,10 @@ void ChatData::setPendingRequestsCount(
|
|||
}
|
||||
|
||||
void ChatData::setAllowedReactions(std::vector<QString> list) {
|
||||
_allowedReactions = std::move(list);
|
||||
if (_allowedReactions != list) {
|
||||
_allowedReactions = std::move(list);
|
||||
session().changes().peerUpdated(this, UpdateFlag::Reactions);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<QString> &ChatData::allowedReactions() const {
|
||||
|
|
|
@ -24,15 +24,19 @@ constexpr auto kRefreshEach = 60 * 60 * crl::time(1000);
|
|||
} // namespace
|
||||
|
||||
Reactions::Reactions(not_null<Session*> owner) : _owner(owner) {
|
||||
request();
|
||||
refresh();
|
||||
|
||||
base::timer_each(
|
||||
kRefreshEach
|
||||
) | rpl::start_with_next([=] {
|
||||
request();
|
||||
refresh();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Reactions::refresh() {
|
||||
request();
|
||||
}
|
||||
|
||||
const std::vector<Reaction> &Reactions::list() const {
|
||||
return _available;
|
||||
}
|
||||
|
@ -47,6 +51,10 @@ std::vector<Reaction> Reactions::list(not_null<PeerData*> peer) const {
|
|||
}
|
||||
}
|
||||
|
||||
rpl::producer<> Reactions::updates() const {
|
||||
return _updated.events();
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::Filtered(
|
||||
const std::vector<Reaction> &reactions,
|
||||
const std::vector<QString> &emoji) {
|
||||
|
|
|
@ -24,6 +24,8 @@ class Reactions final {
|
|||
public:
|
||||
explicit Reactions(not_null<Session*> owner);
|
||||
|
||||
void refresh();
|
||||
|
||||
[[nodiscard]] const std::vector<Reaction> &list() const;
|
||||
[[nodiscard]] std::vector<Reaction> list(not_null<PeerData*> peer) const;
|
||||
|
||||
|
|
|
@ -362,6 +362,7 @@ infoIconAdministrators: icon {{ "info/edit/group_manage_admins", infoIconFg, poi
|
|||
infoIconBlacklist: icon {{ "info_blacklist", infoIconFg, point(-2px, -2px) }};
|
||||
infoIconPermissions: icon {{ "info/edit/group_manage_permissions", infoIconFg, point(0px, -2px) }};
|
||||
infoIconInviteLinks: icon {{ "info/edit/group_manage_links", infoIconFg, point(-2px, 0px) }};
|
||||
infoIconReactions: icon {{ "menu/read_reactions", infoIconFg, point(2px, 4px) }};
|
||||
infoInformationIconPosition: point(25px, 12px);
|
||||
infoNotificationsIconPosition: point(20px, 5px);
|
||||
infoSharedMediaIconPosition: point(20px, 24px);
|
||||
|
@ -707,6 +708,11 @@ editPeerInvitesTopSkip: 10px;
|
|||
editPeerInvitesSkip: 10px;
|
||||
editPeerInviteLinkBoxBottomSkip: 15px;
|
||||
|
||||
editPeerReactionsButton: SettingsButton(infoProfileButton) {
|
||||
padding: margins(59px, 13px, 8px, 11px);
|
||||
}
|
||||
editPeerReactionsPreview: 28px;
|
||||
|
||||
historyTopBarBack: IconButton(infoTopBarBack) {
|
||||
width: 52px;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_keys.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_shared_media.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -401,6 +402,35 @@ rpl::producer<bool> CanAddMemberValue(not_null<PeerData*> peer) {
|
|||
return rpl::single(false);
|
||||
}
|
||||
|
||||
rpl::producer<int> FullReactionsCountValue(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto reactions = &session->data().reactions();
|
||||
return rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
reactions->updates()
|
||||
) | rpl::map([=] {
|
||||
return int(reactions->list().size());
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<int> AllowedReactionsCountValue(not_null<PeerData*> peer) {
|
||||
if (peer->isUser()) {
|
||||
return FullReactionsCountValue(&peer->session());
|
||||
}
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
UpdateFlag::Reactions
|
||||
) | rpl::map([=] {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return int(chat->allowedReactions().size());
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return int(channel->allowedReactions().size());
|
||||
}
|
||||
Unexpected("Peer type in AllowedReactionsCountValue.");
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Flag, typename Peer>
|
||||
rpl::producer<Badge> BadgeValueFromFlags(Peer peer) {
|
||||
return Data::PeerFlagsValue(
|
||||
|
|
|
@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
struct ChannelLocation;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
template <typename Widget>
|
||||
|
@ -63,6 +67,8 @@ rpl::producer<int> SharedMediaCountValue(
|
|||
Storage::SharedMediaType type);
|
||||
rpl::producer<int> CommonGroupsCountValue(not_null<UserData*> user);
|
||||
rpl::producer<bool> CanAddMemberValue(not_null<PeerData*> peer);
|
||||
rpl::producer<int> FullReactionsCountValue(not_null<Main::Session*> peer);
|
||||
rpl::producer<int> AllowedReactionsCountValue(not_null<PeerData*> peer);
|
||||
|
||||
enum class Badge {
|
||||
None,
|
||||
|
|
Loading…
Add table
Reference in a new issue