Compare commits

...

9 Commits
dev ... nightly

20 changed files with 418 additions and 46 deletions

View File

@@ -709,6 +709,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_shortcuts_show_chat_preview" = "Show chat preview";
"lng_shortcuts_record_voice_message" = "Record Voice Message";
"lng_shortcuts_record_round_message" = "Record Round Message";
"lng_shortcuts_admin_log" = "Group/Channel Recent Actions";
"lng_settings_chat_reactions_title" = "Quick Reaction";
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
@@ -4330,6 +4331,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restrict_users_part_single_header" = "What can this user do?";
"lng_restrict_users_part_header#one" = "What can {count} selected user do?";
"lng_restrict_users_part_header#other" = "What can {count} selected users do?";
"lng_restrict_users_kick_from_common_group" = "Also ban from:";
"lng_report_spam" = "Report Spam";
"lng_report_spam_and_leave" = "Report spam and leave";
"lng_report_spam_done" = "Thank you for your report";

View File

@@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_report.h"
#include "apiwrap.h"
#include "data/data_session.h"
#include "data/data_peer.h"
#include "data/data_channel.h"
#include "data/data_photo.h"
#include "data/data_report.h"
#include "data/data_user.h"
@@ -142,4 +144,29 @@ auto CreateReportMessagesOrStoriesCallback(
};
}
void ReportSpam(
not_null<PeerData*> sender,
const MessageIdsList &ids) {
if (ids.empty()) {
return;
}
const auto peer = sender->owner().peer(ids.front().peer);
const auto channel = peer->asChannel();
if (!channel) {
return;
}
auto msgIds = QVector<MTPint>();
msgIds.reserve(ids.size());
for (const auto &fullId : ids) {
msgIds.push_back(MTP_int(fullId.msg));
}
sender->session().api().request(MTPchannels_ReportSpam(
channel->inputChannel(),
sender->input(),
MTP_vector<MTPint>(msgIds)
)).send();
}
} // namespace Api

View File

@@ -53,4 +53,8 @@ void SendPhotoReport(
not_null<PeerData*> peer)
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)>;
void ReportSpam(
not_null<PeerData*> sender,
const MessageIdsList &ids);
} // namespace Api

View File

@@ -1165,3 +1165,20 @@ fakeUserpicButton: UserpicButton(defaultUserpicButton) {
changeIcon: icon {{ "settings/photo", transparent }};
uploadBg: transparent;
}
moderateCommonGroupsCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) {
imageRadius: 12px;
imageSmallRadius: 11px;
selectWidth: 2px;
check: RoundCheckbox(defaultPeerListCheck) {
size: 16px;
sizeSmall: 0.3;
bgInactive: overviewCheckBg;
bgActive: overviewCheckBgActive;
check: icon {{
"default_checkbox_check",
overviewCheckFgActive,
point(1px, 4px)
}};
}
}

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_chat_participants.h"
#include "api/api_messages_search.h"
#include "api/api_report.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/core_settings.h"
@@ -632,12 +633,7 @@ void DeleteMessagesBox::deleteAndClear() {
ChatRestrictionsInfo());
}
if (_reportSpam->checked()) {
_moderateInChannel->session().api().request(
MTPchannels_ReportSpam(
_moderateInChannel->inputChannel(),
_moderateFrom->input(),
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
).send();
Api::ReportSpam(_moderateFrom, { _ids[0] });
}
if (_deleteAll && _deleteAll->checked()) {
_moderateInChannel->session().api().deleteAllFromParticipant(

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_blocked_peers.h"
#include "api/api_chat_participants.h"
#include "api/api_messages_search.h"
#include "api/api_report.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "base/timer.h"
@@ -49,6 +50,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_window.h"
#include "boxes/peer_list_box.h"
#include "main/main_session_settings.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/effects/round_checkbox.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
namespace {
struct ModerateOptions final {
@@ -132,6 +144,155 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
};
}
using CommonGroups = std::vector<not_null<PeerData*>>;
using CollectCommon = std::shared_ptr<std::vector<PeerId>>;
void FillMenuModerateCommonGroups(
not_null<Ui::PopupMenu*> menu,
CommonGroups common,
CollectCommon collectCommon) {
const auto resultList
= menu->lifetime().make_state<base::flat_set<PeerId>>();
const auto rememberCheckbox = Ui::CreateChild<Ui::Checkbox>(
menu,
QString());
auto multiline = base::make_unique_q<Ui::Menu::MultilineAction>(
menu->menu(),
menu->st().menu,
st::historyHasCustomEmoji,
st::historyHasCustomEmojiPosition,
tr::lng_restrict_users_kick_from_common_group(tr::now, tr::rich));
multiline->setDisabled(true);
multiline->setAttribute(Qt::WA_TransparentForMouseEvents);
menu->addAction(std::move(multiline));
const auto session = &common.front()->session();
const auto save = [=] {
auto result = std::vector<PeerId>(
resultList->begin(),
resultList->end());
if (rememberCheckbox->checked()) {
session->settings().setModerateCommonGroups(result);
session->saveSettingsDelayed();
}
*collectCommon = std::move(result);
};
for (const auto &group : common) {
struct State {
std::optional<Ui::RoundImageCheckbox> checkbox;
Ui::RpWidget *checkboxWidget = nullptr;
};
auto item = base::make_unique_q<Ui::Menu::Action>(
menu->menu(),
menu->st().menu,
Ui::Menu::CreateAction(
menu->menu(),
group->name(),
[] {}),
nullptr,
nullptr);
const auto state = item->lifetime().make_state<State>();
item->AbstractButton::setDisabled(true);
item->Ui::Menu::ItemBase::setClickedCallback([=, peerId = group->id] {
state->checkbox->setChecked(!state->checkbox->checked());
if (state->checkbox->checked()) {
resultList->insert(peerId);
} else {
resultList->erase(peerId);
}
save();
});
const auto raw = item.get();
state->checkboxWidget = Ui::CreateChild<Ui::RpWidget>(raw);
state->checkboxWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
state->checkboxWidget->resize(item->width() * 2, item->height());
state->checkboxWidget->show();
state->checkbox.emplace(
st::moderateCommonGroupsCheckbox,
[=] { state->checkboxWidget->update(); },
PaintUserpicCallback(group, true),
[=](int size) { return (group->isForum() || group->isMonoforum())
? int(size * Ui::ForumUserpicRadiusMultiplier())
: std::optional<int>(); });
state->checkbox->setChecked(
ranges::contains(
session->settings().moderateCommonGroups(),
group->id)
|| (collectCommon
&& ranges::contains(*collectCommon, group->id)),
anim::type::instant);
state->checkboxWidget->paintOn([=](QPainter &p) {
auto pp = Painter(state->checkboxWidget);
state->checkbox->paint(
pp,
st::menuWithIcons.itemIconPosition.x(),
st::menuWithIcons.itemIconPosition.y(),
raw->width());
});
menu->addAction(std::move(item));
}
menu->addSeparator();
{
auto item = base::make_unique_q<Ui::Menu::Action>(
menu->menu(),
menu->st().menu,
Ui::Menu::CreateAction(
menu->menu(),
tr::lng_remember(tr::now),
[] {}),
nullptr,
nullptr);
item->AbstractButton::setDisabled(true);
item->Ui::Menu::ItemBase::setClickedCallback([=] {
rememberCheckbox->setChecked(!rememberCheckbox->checked());
});
rememberCheckbox->setParent(item.get());
rememberCheckbox->setAttribute(Qt::WA_TransparentForMouseEvents);
rememberCheckbox->move(st::lineWidth * 8, -st::lineWidth * 2);
rememberCheckbox->show();
menu->addAction(std::move(item));
}
}
void ProccessCommonGroups(
const HistoryItemsList &items,
Fn<void(CommonGroups)> processHas) {
const auto moderateOptions = CalculateModerateOptions(items);
if (moderateOptions.participants.size() != 1
|| !moderateOptions.allCanBan) {
return;
}
const auto participant = moderateOptions.participants.front();
const auto user = participant->asUser();
if (!user) {
return;
}
const auto currentGroupId = items.front()->history()->peer->id;
user->session().api().requestBotCommonGroups(user, [=] {
const auto commonGroups = user->session().api().botCommonGroups(user);
if (!commonGroups || commonGroups->empty()) {
return;
}
auto filtered = CommonGroups();
for (const auto &group : *commonGroups) {
if (group->id == currentGroupId) {
continue;
}
const auto channel = group->asChannel();
if (channel && channel->canRestrictParticipant(user)) {
if (channel->isGroupAdmin(user) && !channel->amCreator()) {
continue;
}
filtered.push_back(group);
}
}
if (!filtered.empty()) {
processHas(filtered);
}
});
}
} // namespace
void CreateModerateMessagesBox(
@@ -149,6 +310,7 @@ void CreateModerateMessagesBox(
Assert(!participants.empty());
const auto confirms = inner->lifetime().make_state<rpl::event_stream<>>();
const auto collectCommon = std::make_shared<std::vector<PeerId>>();
const auto isSingle = participants.size() == 1;
const auto buttonPadding = isSingle
@@ -167,29 +329,64 @@ void CreateModerateMessagesBox(
const auto historyPeerId = history->peer->id;
const auto ids = session->data().itemsToIds(items);
ProccessCommonGroups(
items,
[=](CommonGroups groups) {
using namespace Ui;
const auto top = box->addTopButton(st::infoTopBarMenu);
auto &lifetime = top->lifetime();
const auto menu
= lifetime.make_state<base::unique_qptr<Ui::PopupMenu>>();
top->setClickedCallback([=] {
top->setForceRippled(true);
*menu = base::make_unique_q<Ui::PopupMenu>(
top,
st::popupMenuExpandedSeparator);
(*menu)->setDestroyedCallback([=, weak = top] {
if (const auto strong = weak.data()) {
strong->setForceRippled(false);
}
});
FillMenuModerateCommonGroups(*menu, groups, collectCommon);
(*menu)->setForcedOrigin(PanelAnimation::Origin::TopRight);
const auto point = QPoint(top->width(), top->height());
(*menu)->popup(top->mapToGlobal(point));
});
});
using Request = Fn<void(not_null<PeerData*>, not_null<ChannelData*>)>;
const auto sequentiallyRequest = [=](
Request request,
Participants participants) {
Participants participants,
std::optional<std::vector<PeerId>> channelIds = {}) {
constexpr auto kSmallDelayMs = 5;
const auto participantIds = ranges::views::all(
participants
) | ranges::views::transform([](not_null<PeerData*> peer) {
return peer->id;
}) | ranges::to_vector;
const auto channelIdList = channelIds.value_or(
std::vector<PeerId>{ historyPeerId });
const auto lifetime = std::make_shared<rpl::lifetime>();
const auto counter = lifetime->make_state<int>(0);
const auto participantIndex = lifetime->make_state<int>(0);
const auto channelIndex = lifetime->make_state<int>(0);
const auto timer = lifetime->make_state<base::Timer>();
timer->setCallback(crl::guard(session, [=] {
if ((*counter) < participantIds.size()) {
const auto peer = session->data().peer(historyPeerId);
const auto channel = peer ? peer->asChannel() : nullptr;
const auto from = session->data().peer(
participantIds[*counter]);
if (channel && from) {
request(from, channel);
if ((*participantIndex) < participantIds.size()) {
if ((*channelIndex) < channelIdList.size()) {
const auto from = session->data().peer(
participantIds[*participantIndex]);
const auto channel = session->data().peer(
channelIdList[*channelIndex])->asChannel();
if (from && channel) {
request(from, channel);
}
(*channelIndex)++;
} else {
(*participantIndex)++;
*channelIndex = 0;
}
(*counter)++;
} else {
lifetime->destroy();
}
@@ -273,17 +470,7 @@ void CreateModerateMessagesBox(
handleConfirmation(report, controller, [=](
not_null<PeerData*> p,
not_null<ChannelData*> c) {
auto filtered = ranges::views::all(
ids
) | ranges::views::transform([](const FullMsgId &id) {
return MTP_int(id.msg);
}) | ranges::to<QVector<MTPint>>();
c->session().api().request(
MTPchannels_ReportSpam(
c->inputChannel(),
p->input(),
MTP_vector<MTPint>(std::move(filtered)))
).send();
Api::ReportSpam(p, ids);
});
}
@@ -548,7 +735,16 @@ void CreateModerateMessagesBox(
}
}
};
sequentiallyRequest(request, controller->collectRequests());
if (collectCommon && !collectCommon->empty()) {
sequentiallyRequest(
request,
controller->collectRequests(),
*collectCommon);
} else {
sequentiallyRequest(
request,
controller->collectRequests());
}
}
}, ban->lifetime());
}

View File

@@ -33,10 +33,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/text/text_utilities.h"
#include "ui/rect.h"
#include "info/profile/info_profile_values.h"
#include "window/window_session_controller.h"
#include "history/history.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace {
@@ -1212,6 +1214,17 @@ rpl::producer<int> ParticipantsBoxController::fullCountValue() const {
return _fullCountValue.value();
}
rpl::producer<int> ParticipantsBoxController::boxHeightValue() const {
return _fullCountValue.value() | rpl::map([=](int count) {
const auto &st = computeListSt();
const auto searchHeight = st.item.height;
const auto listHeight = count * st.item.height;
return std::max(
searchHeight + listHeight + rect::m::sum::v(st.padding),
st::boxMaxListHeight);
});
}
void ParticipantsBoxController::setStoriesShown(bool shown) {
_stories = std::make_unique<PeerListStories>(
this,
@@ -1902,6 +1915,9 @@ void ParticipantsBoxController::editRestrictedDone(
void ParticipantsBoxController::kickParticipant(not_null<PeerData*> participant) {
const auto user = participant->asUser();
if (user && user->isInaccessible()) {
return kickParticipantSure(participant);
}
const auto text = ((_peer->isChat() || _peer->isMegagroup())
? tr::lng_profile_sure_kick
: tr::lng_profile_sure_kick_channel)(
@@ -2128,7 +2144,12 @@ auto ParticipantsBoxController::computeType(
: (user && _additional.adminRights(user).has_value())
? Rights::Admin
: Rights::Normal;
result.adminRank = user ? _additional.adminRank(user) : QString();
result.canRemove = _additional.canRemoveParticipant(participant)
&& user
&& user->isInaccessible();
if (!result.canRemove) {
result.adminRank = user ? _additional.adminRank(user) : QString();
}
return result;
}

View File

@@ -202,6 +202,7 @@ public:
[[nodiscard]] rpl::producer<int> onlineCountValue() const;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
[[nodiscard]] rpl::producer<int> boxHeightValue() const override;
void setStoriesShown(bool shown);

View File

@@ -136,6 +136,7 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"show_scheduled"_q , Command::ShowScheduled },
{ u"archive_chat"_q , Command::ArchiveChat },
{ u"record_round"_q , Command::RecordRound },
{ u"show_admin_log"_q , Command::ShowAdminLog },
//
};

View File

@@ -80,6 +80,8 @@ enum class Command {
ShowChatMenu,
ShowChatPreview,
ShowAdminLog,
SupportReloadTemplates,
SupportToggleMuted,
SupportScrollToCurrent,

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_chat_participants.h"
#include "api/api_attached_stickers.h"
#include "api/api_report.h"
#include "window/window_session_controller.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
@@ -916,6 +917,7 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
: newItemsForDownDirection;
addToItems.reserve(oldItemsCount + events.size() * 2);
const auto canRestrict = InnerWidget::canRestrict();
const auto antiSpamUserId = _antiSpamValidator.userId();
for (const auto &event : events) {
const auto &data = event.data();
@@ -936,10 +938,15 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
}
_eventIds.emplace(id);
_itemsByData.emplace(item->data(), item.get());
if (rememberRealMsgId && realId) {
_antiSpamValidator.addEventMsgId(
item->data()->fullId(),
realId);
if (realId) {
if (rememberRealMsgId) {
_antiSpamValidator.addEventMsgId(
item->data()->fullId(),
realId);
}
if (canRestrict) {
_realIdsForReport[item->data()->fullId()] = realId;
}
}
addToItems.push_back(std::move(item));
++count;
@@ -1382,7 +1389,15 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
} else if (fromId) { // suggest to block
if (const auto participant = session().data().peer(fromId)) {
suggestRestrictParticipant(participant);
const auto item = view ? view->data().get() : nullptr;
auto realId = FullMsgId();
if (const auto itemId = item ? item->fullId() : FullMsgId()) {
const auto it = _realIdsForReport.find(itemId);
if (it != _realIdsForReport.end()) {
realId = FullMsgId(_channel->id, it->second);
}
}
suggestRestrictParticipant(participant, realId);
}
} else { // maybe cursor on some text history item?
const auto item = view ? view->data().get() : nullptr;
@@ -1532,12 +1547,11 @@ void InnerWidget::copyContextText(FullMsgId itemId) {
}
void InnerWidget::suggestRestrictParticipant(
not_null<PeerData*> participant) {
not_null<PeerData*> participant,
FullMsgId realId) {
Expects(_menu != nullptr);
if (!_channel->isMegagroup()
|| !_channel->canBanMembers()
|| _admins.empty()) {
if (!canRestrict()) {
return;
}
if (ranges::contains(_admins, participant)) {
@@ -1640,12 +1654,26 @@ void InnerWidget::suggestRestrictParticipant(
participant,
{ _channel->restrictions(), 0 });
};
Ui::Menu::CreateAddActionCallback(_menu)({
const auto addAction = Ui::Menu::CreateAddActionCallback(_menu);
addAction({
.text = tr::lng_context_ban_user(tr::now),
.handler = std::move(handler),
.handler = handler,
.icon = &st::menuIconBlockAttention,
.isAttention = true,
});
if (realId) {
addAction({
.text = tr::lng_report_and_ban(tr::now),
.handler = [=, show = _controller->uiShow()] {
Api::ReportSpam(participant, { realId });
handler();
show->showToast(tr::lng_report_spam_done(tr::now));
},
.icon = &st::menuIconReportAttention,
.isAttention = true,
});
}
}
}
@@ -1682,6 +1710,12 @@ void InnerWidget::restrictParticipantDone(
checkPreloadMore();
}
bool InnerWidget::canRestrict() const {
return _channel->isMegagroup()
&& _channel->canBanMembers()
&& !_admins.empty();
}
void InnerWidget::mousePressEvent(QMouseEvent *e) {
if (_menu) {
e->accept();

View File

@@ -213,7 +213,9 @@ private:
void copyContextText(FullMsgId itemId);
void copySelectedText();
TextForMimeData getSelectedText() const;
void suggestRestrictParticipant(not_null<PeerData*> participant);
void suggestRestrictParticipant(
not_null<PeerData*> participant,
FullMsgId realId);
void restrictParticipant(
not_null<PeerData*> participant,
ChatRestrictionsInfo oldRights,
@@ -221,6 +223,7 @@ private:
void restrictParticipantDone(
not_null<PeerData*> participant,
ChatRestrictionsInfo rights);
[[nodiscard]] bool canRestrict() const;
void requestAdmins();
void checkPreloadMore();
@@ -286,6 +289,7 @@ private:
base::flat_set<FullMsgId> _animatedStickersPlayed;
base::flat_map<not_null<PeerData*>, Ui::PeerUserpicView> _userpics;
base::flat_map<not_null<PeerData*>, Ui::PeerUserpicView> _userpicsCache;
base::flat_map<FullMsgId, MsgId> _realIdsForReport;
int _itemsTop = 0;
int _itemsWidth = 0;
int _itemsHeight = 0;

View File

@@ -95,6 +95,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_inner_widget.h"
#include "history/history_item_components.h"
#include "history/history_unread_things.h"
#include "history/admin_log/history_admin_log_section.h"
#include "history/view/controls/history_view_characters_limit.h"
#include "history/view/controls/history_view_compose_search.h"
#include "history/view/controls/history_view_forward_panel.h"
@@ -2149,6 +2150,16 @@ void HistoryWidget::setupShortcuts() {
return false;
});
}
const auto channel = _peer ? _peer->asChannel() : nullptr;
const auto hasRecentActions = channel
&& (channel->hasAdminRights() || channel->amCreator());
if (hasRecentActions) {
request->check(Command::ShowAdminLog, 1) && request->handle([=] {
controller()->showSection(
std::make_shared<AdminLog::SectionMemento>(channel));
return true;
});
}
if (session().supportMode()) {
request->check(
Command::SupportToggleMuted

View File

@@ -30,7 +30,9 @@ MemberListRow::MemberListRow(
void MemberListRow::setType(Type type) {
_type = type;
PeerListRowWithLink::setActionLink(!_type.adminRank.isEmpty()
PeerListRowWithLink::setActionLink(_type.canRemove
? tr::lng_profile_delete_removed(tr::now)
: !_type.adminRank.isEmpty()
? _type.adminRank
: (_type.rights == Rights::Creator)
? tr::lng_owner_badge(tr::now)
@@ -44,11 +46,19 @@ MemberListRow::Type MemberListRow::type() const {
}
bool MemberListRow::rightActionDisabled() const {
return true;
return !canRemove();
}
QMargins MemberListRow::rightActionMargins() const {
const auto skip = st::contactsCheckPosition.x();
if (canRemove()) {
const auto &st = st::defaultPeerListItem;
return QMargins(
skip,
(st.height - st.nameStyle.font->height) / 2,
st.photoPosition.x() + skip,
0);
}
return QMargins(
skip,
st::defaultPeerListItem.namePosition.y(),
@@ -72,6 +82,10 @@ void MemberListRow::refreshStatus() {
}
}
bool MemberListRow::canRemove() const {
return _type.canRemove;
}
std::unique_ptr<ParticipantsBoxController> CreateMembersController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {

View File

@@ -28,6 +28,7 @@ public:
};
struct Type {
Rights rights;
bool canRemove = false;
QString adminRank;
};
@@ -42,6 +43,7 @@ public:
not_null<UserData*> user() const;
private:
[[nodiscard]] bool canRemove() const;
Type _type;
};

View File

@@ -66,6 +66,8 @@ QByteArray SessionSettings::serialize() const {
+ Serialize::stringSize(auth.location);
}
size += sizeof(qint32); // _setupEmailState
size += sizeof(qint32) // _moderateCommonGroups size
+ (_moderateCommonGroups.size() * sizeof(quint64));
auto result = QByteArray();
result.reserve(size);
@@ -147,6 +149,10 @@ QByteArray SessionSettings::serialize() const {
<< auth.location;
}
stream << qint32(static_cast<int>(_setupEmailState));
stream << qint32(_moderateCommonGroups.size());
for (const auto &peerId : _moderateCommonGroups) {
stream << SerializePeerId(peerId);
}
}
Ensures(result.size() == size);
@@ -219,6 +225,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
base::flat_set<uint64> ratedTranscriptions;
std::vector<Data::UnreviewedAuth> unreviewed;
qint32 setupEmailState = 0;
std::vector<PeerId> moderateCommonGroups;
stream >> versionTag;
if (versionTag == kVersionTag) {
@@ -635,6 +642,23 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> setupEmailState;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
quint64 peerId;
stream >> peerId;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"
"with moderateCommonGroups"));
return;
}
moderateCommonGroups.emplace_back(DeserializePeerId(peerId));
}
}
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
@@ -698,6 +722,8 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
break;
}
_moderateCommonGroups = std::move(moderateCommonGroups);
if (version < 2) {
app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1);
for (const auto &[key, value] : appSoundOverrides) {

View File

@@ -176,6 +176,13 @@ public:
void setSetupEmailState(Data::SetupEmailState state);
[[nodiscard]] Data::SetupEmailState setupEmailState() const;
void setModerateCommonGroups(std::vector<PeerId> groups) {
_moderateCommonGroups = std::move(groups);
}
[[nodiscard]] const std::vector<PeerId> &moderateCommonGroups() const {
return _moderateCommonGroups;
}
private:
static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60;
static constexpr auto kPhotoEditorHintMaxShowsCount = 5;
@@ -222,6 +229,8 @@ private:
Data::SetupEmailState _setupEmailState;
std::vector<PeerId> _moderateCommonGroups;
};
} // namespace Main

View File

@@ -312,9 +312,12 @@ void GlobalTTL::rebuildButtons(TimeId currentTTL) const {
QString());
radio->setAttribute(Qt::WA_TransparentForMouseEvents);
radio->show();
const auto padding = button->st().padding;
button->sizeValue(
) | rpl::on_next([=] {
radio->moveToRight(0, radio->checkRect().top());
) | rpl::on_next([=](QSize s) {
radio->moveToLeft(
s.width() - radio->checkRect().width() - padding.left(),
radio->checkRect().top());
}, radio->lifetime());
}
_buttons->resizeToWidth(width());

View File

@@ -107,6 +107,8 @@ struct Labeled {
{ C::RecordVoice, tr::lng_shortcuts_record_voice_message() },
{ C::RecordRound, tr::lng_shortcuts_record_round_message() },
separator,
{ C::ShowAdminLog, tr::lng_shortcuts_admin_log() },
separator,
{ C::MediaViewerFullscreen, tr::lng_shortcuts_media_fullscreen() },
separator,
{ C::MediaPlay, tr::lng_shortcuts_media_play() },