mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +02:00
Start filter share links management.
This commit is contained in:
parent
725c22e776
commit
b7d9d549ff
19 changed files with 1347 additions and 69 deletions
|
@ -173,6 +173,8 @@ PRIVATE
|
|||
boxes/filters/edit_filter_box.h
|
||||
boxes/filters/edit_filter_chats_list.cpp
|
||||
boxes/filters/edit_filter_chats_list.h
|
||||
boxes/filters/edit_filter_links.cpp
|
||||
boxes/filters/edit_filter_links.h
|
||||
boxes/peers/add_bot_to_chat_box.cpp
|
||||
boxes/peers/add_bot_to_chat_box.h
|
||||
boxes/peers/add_participants_box.cpp
|
||||
|
|
BIN
Telegram/Resources/icons/settings/folder_links.png
Normal file
BIN
Telegram/Resources/icons/settings/folder_links.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 467 B |
BIN
Telegram/Resources/icons/settings/folder_links@2x.png
Normal file
BIN
Telegram/Resources/icons/settings/folder_links@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 883 B |
BIN
Telegram/Resources/icons/settings/folder_links@3x.png
Normal file
BIN
Telegram/Resources/icons/settings/folder_links@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -3541,6 +3541,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_filters_toast_add" = "{chat} added to {folder} folder";
|
||||
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
|
||||
|
||||
"lng_filters_link" = "Invite Link";
|
||||
"lng_filters_link_create" = "Share Folder";
|
||||
"lng_filters_link_add" = "Add Link";
|
||||
"lng_filters_link_cant" = "You can only share folders that have several explicit chats selected, no general chat types and no exclusions.";
|
||||
"lng_filters_link_about" = "Share access to some of this folder's groups and channels with others.";
|
||||
"lng_filters_link_title" = "Share Folder";
|
||||
"lng_filters_link_select" = "Select the chats you want to share with the link.";
|
||||
"lng_filters_by_link_title" = "Add Folder";
|
||||
"lng_filters_by_link_sure" = "Do you want to add a new chat folder {folder} and join its groups and channels?";
|
||||
"lng_filters_by_link_join#one" = "{count} chat to join";
|
||||
"lng_filters_by_link_join#other" = "{count} chats to join";
|
||||
"lng_filters_by_link_add" = "Add {folder}";
|
||||
"lng_filters_by_link_more" = "Add Chats to Folder";
|
||||
"lng_filters_by_link_more_sure" = "Do you want to join chats and add them to your folder {folder}?";
|
||||
"lng_filters_by_link_about" = "You can deselect the chats you don't want to join.";
|
||||
"lng_filters_by_link_join_button" = "Join Chats";
|
||||
"lng_filters_by_link_remove" = "Remove Folder";
|
||||
"lng_filters_by_link_remove_sure" = "Do you want to quit the chats you joined when added the folder {folder}?";
|
||||
"lng_filters_by_link_quit#one" = "{count} chat to quit";
|
||||
"lng_filters_by_link_quit#other" = "{count} chats to quit";
|
||||
"lng_filters_by_link_select" = "Select All";
|
||||
"lng_filters_by_link_deselect" = "Deselect All";
|
||||
"lng_filters_by_link_remove_button" = "Remove Folder";
|
||||
"lng_filters_by_link_quit_button" = "Remove Folder and Chats";
|
||||
|
||||
"lng_chat_theme_change" = "Change colors";
|
||||
"lng_chat_theme_none" = "No\nTheme";
|
||||
"lng_chat_theme_apply" = "Apply Theme";
|
||||
|
|
|
@ -8,20 +8,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/filters/edit_filter_box.h"
|
||||
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
#include "boxes/filters/edit_filter_links.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/effects/panel_animation.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/filter_icon_panel.h"
|
||||
#include "ui/painter.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "settings/settings_common.h"
|
||||
|
@ -37,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_layers.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -499,6 +505,24 @@ void CreateIconSelector(
|
|||
return QString();
|
||||
}
|
||||
|
||||
not_null<Ui::SettingsButton*> AddToggledButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<bool> shown,
|
||||
rpl::producer<QString> text,
|
||||
const style::SettingsButton &st,
|
||||
IconDescriptor &&descriptor) {
|
||||
const auto toggled = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
container,
|
||||
CreateButton(
|
||||
container,
|
||||
std::move(text),
|
||||
st,
|
||||
std::move(descriptor)))
|
||||
)->toggleOn(std::move(shown), anim::type::instant)->setDuration(0);
|
||||
return toggled->entity();
|
||||
}
|
||||
|
||||
[[nodiscard]] QString TrimDefaultTitle(const QString &title) {
|
||||
return (title.size() <= kMaxFilterTitleLength) ? title : QString();
|
||||
}
|
||||
|
@ -509,7 +533,12 @@ void EditFilterBox(
|
|||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter,
|
||||
Fn<void(const Data::ChatFilter &)> doneCallback) {
|
||||
Fn<void(const Data::ChatFilter &)> doneCallback,
|
||||
Fn<void(
|
||||
const Data::ChatFilter &data,
|
||||
Fn<void(Data::ChatFilter)> next)> saveAnd) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto creating = filter.title().isEmpty();
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
|
||||
|
@ -521,8 +550,27 @@ void EditFilterBox(
|
|||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
|
||||
using State = rpl::variable<Data::ChatFilter>;
|
||||
const auto data = box->lifetime().make_state<State>(filter);
|
||||
struct State {
|
||||
rpl::variable<Data::ChatFilter> rules;
|
||||
rpl::variable<std::vector<Data::ChatFilterLink>> links;
|
||||
rpl::variable<bool> hasLinks;
|
||||
rpl::variable<bool> community;
|
||||
};
|
||||
const auto owner = &window->session().data();
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.rules = filter,
|
||||
.community = filter.community(),
|
||||
});
|
||||
state->links = owner->chatsFilters().communityLinks(filter.id()),
|
||||
state->hasLinks = state->links.value() | rpl::map([=](const auto &v) {
|
||||
return !v.empty();
|
||||
});
|
||||
if (!state->community.current()) {
|
||||
state->community = state->hasLinks.value() | rpl::filter(
|
||||
_1
|
||||
) | rpl::take(1);
|
||||
}
|
||||
const auto data = &state->rules;
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
const auto name = content->add(
|
||||
|
@ -604,24 +652,123 @@ void EditFilterBox(
|
|||
AddDividerText(content, tr::lng_filters_include_about());
|
||||
AddSkip(content);
|
||||
|
||||
AddSubsectionTitle(content, tr::lng_filters_exclude());
|
||||
auto excludeWrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
content,
|
||||
object_ptr<Ui::VerticalLayout>(content))
|
||||
)->setDuration(0);
|
||||
excludeWrap->toggleOn(state->community.value() | rpl::map(!_1));
|
||||
const auto excludeInner = excludeWrap->entity();
|
||||
|
||||
AddSubsectionTitle(excludeInner, tr::lng_filters_exclude());
|
||||
|
||||
const auto excludeAdd = AddButton(
|
||||
content,
|
||||
excludeInner,
|
||||
tr::lng_filters_remove_chats(),
|
||||
st::settingsButtonActive,
|
||||
{ &st::settingsIconRemove, 0, IconType::Round, &st::windowBgActive });
|
||||
|
||||
const auto exclude = SetupChatsPreview(
|
||||
content,
|
||||
excludeInner,
|
||||
data,
|
||||
updateDefaultTitle,
|
||||
kExcludeTypes,
|
||||
&Data::ChatFilter::never);
|
||||
|
||||
AddSkip(content);
|
||||
AddDividerText(content, tr::lng_filters_exclude_about());
|
||||
AddSkip(excludeInner);
|
||||
AddDividerText(excludeInner, tr::lng_filters_exclude_about());
|
||||
AddSkip(excludeInner);
|
||||
|
||||
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
|
||||
const auto title = name->getLastText().trimmed();
|
||||
const auto rules = data->current();
|
||||
const auto result = Data::ChatFilter(
|
||||
rules.id(),
|
||||
title,
|
||||
rules.iconEmoji(),
|
||||
rules.flags(),
|
||||
rules.always(),
|
||||
rules.pinned(),
|
||||
rules.never());
|
||||
if (title.isEmpty()) {
|
||||
name->showError();
|
||||
return {};
|
||||
} else if (!(rules.flags() & kTypes) && rules.always().empty()) {
|
||||
window->window().showToast(tr::lng_filters_empty(tr::now));
|
||||
return {};
|
||||
} else if ((rules.flags() == (kTypes | Flag::NoArchived))
|
||||
&& rules.always().empty()
|
||||
&& rules.never().empty()) {
|
||||
window->window().showToast(tr::lng_filters_default(tr::now));
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
AddSubsectionTitle(content, tr::lng_filters_link());
|
||||
|
||||
if (filter.community()) {
|
||||
window->session().data().chatsFilters().reloadCommunityLinks(
|
||||
filter.id());
|
||||
}
|
||||
|
||||
const auto createLink = AddToggledButton(
|
||||
content,
|
||||
state->hasLinks.value() | rpl::map(!rpl::mappers::_1),
|
||||
tr::lng_filters_link_create(),
|
||||
st::settingsButtonActive,
|
||||
{ &st::settingsFolderShareIcon, 0, IconType::Simple });
|
||||
const auto addLink = AddToggledButton(
|
||||
content,
|
||||
state->hasLinks.value(),
|
||||
tr::lng_group_invite_add(),
|
||||
st::settingsButtonActive,
|
||||
{ &st::settingsIconAdd, 0, IconType::Round, &st::windowBgActive });
|
||||
|
||||
SetupFilterLinks(
|
||||
content,
|
||||
window,
|
||||
state->links.value(),
|
||||
[=] { return collect().value_or(Data::ChatFilter()); });
|
||||
|
||||
rpl::merge(
|
||||
createLink->clicks(),
|
||||
addLink->clicks()
|
||||
) | rpl::filter(
|
||||
(rpl::mappers::_1 == Qt::LeftButton)
|
||||
) | rpl::start_with_next([=](Qt::MouseButton button) {
|
||||
const auto result = collect();
|
||||
if (!result || !GoodForExportFilterLink(window, *result)) {
|
||||
return;
|
||||
}
|
||||
const auto shared = CollectFilterLinkChats(*result);
|
||||
if (shared.empty()) {
|
||||
// langs
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = Window::Show(window).toastParent(),
|
||||
.text = { tr::lng_filters_link_cant(tr::now) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
saveAnd(*result, crl::guard(box, [=](Data::ChatFilter updated) {
|
||||
box->setTitle(tr::lng_filters_edit());
|
||||
nameEditing->custom = true;
|
||||
|
||||
*data = updated;
|
||||
const auto id = updated.id();
|
||||
state->links = owner->chatsFilters().communityLinks(id);
|
||||
ExportFilterLink(id, shared, [=](Data::ChatFilterLink link) {
|
||||
Expects(link.id == id);
|
||||
|
||||
window->show(ShowLinkBox(window, updated, link));
|
||||
});
|
||||
}));
|
||||
}, createLink->lifetime());
|
||||
|
||||
AddSkip(content);
|
||||
AddDividerText(content, tr::lng_filters_link_about());
|
||||
|
||||
const auto show = std::make_shared<Ui::BoxShow>(box);
|
||||
const auto refreshPreviews = [=] {
|
||||
include->updateData(
|
||||
data->current().flags() & kTypes,
|
||||
|
@ -634,7 +781,7 @@ void EditFilterBox(
|
|||
EditExceptions(
|
||||
window,
|
||||
box,
|
||||
kTypes,
|
||||
kTypes | (state->community.current() ? Flag::Community : Flag()),
|
||||
data,
|
||||
updateDefaultTitle,
|
||||
refreshPreviews);
|
||||
|
@ -650,32 +797,12 @@ void EditFilterBox(
|
|||
});
|
||||
|
||||
const auto save = [=] {
|
||||
const auto title = name->getLastText().trimmed();
|
||||
const auto rules = data->current();
|
||||
const auto result = Data::ChatFilter(
|
||||
rules.id(),
|
||||
title,
|
||||
rules.iconEmoji(),
|
||||
rules.flags(),
|
||||
rules.always(),
|
||||
rules.pinned(),
|
||||
rules.never());
|
||||
if (title.isEmpty()) {
|
||||
name->showError();
|
||||
return;
|
||||
} else if (!(rules.flags() & kTypes) && rules.always().empty()) {
|
||||
window->window().showToast(tr::lng_filters_empty(tr::now));
|
||||
return;
|
||||
} else if ((rules.flags() == (kTypes | Flag::NoArchived))
|
||||
&& rules.always().empty()
|
||||
&& rules.never().empty()) {
|
||||
window->window().showToast(tr::lng_filters_default(tr::now));
|
||||
return;
|
||||
if (const auto result = collect()) {
|
||||
box->closeBox();
|
||||
doneCallback(*result);
|
||||
}
|
||||
box->closeBox();
|
||||
|
||||
doneCallback(result);
|
||||
};
|
||||
|
||||
box->addButton(
|
||||
creating ? tr::lng_filters_create_button() : tr::lng_settings_save(),
|
||||
save);
|
||||
|
@ -707,9 +834,16 @@ void EditExistingFilter(
|
|||
tl
|
||||
)).send();
|
||||
};
|
||||
const auto saveAnd = [=](
|
||||
const Data::ChatFilter &data,
|
||||
Fn<void(Data::ChatFilter)> next) {
|
||||
doneCallback(data);
|
||||
next(data);
|
||||
};
|
||||
window->window().show(Box(
|
||||
EditFilterBox,
|
||||
window,
|
||||
*i,
|
||||
crl::guard(session, doneCallback)));
|
||||
crl::guard(session, doneCallback),
|
||||
crl::guard(session, saveAnd)));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@ void EditFilterBox(
|
|||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter,
|
||||
Fn<void(const Data::ChatFilter &)> doneCallback);
|
||||
Fn<void(const Data::ChatFilter &)> doneCallback,
|
||||
Fn<void(const Data::ChatFilter &, Fn<void(Data::ChatFilter)>)> saveAnd);
|
||||
|
||||
void EditExistingFilter(
|
||||
not_null<Window::SessionController*> window,
|
||||
|
|
|
@ -343,9 +343,10 @@ EditFilterChatsListController::EditFilterChatsListController(
|
|||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _peers(peers)
|
||||
, _options(options)
|
||||
, _options(options & ~Flag::Community)
|
||||
, _selected(selected)
|
||||
, _limit(Limit(session)) {
|
||||
, _limit(Limit(session))
|
||||
, _community(options & Flag::Community) {
|
||||
}
|
||||
|
||||
Main::Session &EditFilterChatsListController::session() const {
|
||||
|
@ -353,8 +354,11 @@ Main::Session &EditFilterChatsListController::session() const {
|
|||
}
|
||||
|
||||
int EditFilterChatsListController::selectedTypesCount() const {
|
||||
Expects(_typesDelegate != nullptr);
|
||||
Expects(_community || _typesDelegate != nullptr);
|
||||
|
||||
if (_community) {
|
||||
return 0;
|
||||
}
|
||||
auto result = 0;
|
||||
for (auto i = 0; i != _typesDelegate->peerListFullRowsCount(); ++i) {
|
||||
if (_typesDelegate->peerListRowAt(i)->checked()) {
|
||||
|
@ -396,7 +400,9 @@ bool EditFilterChatsListController::handleDeselectForeignRow(
|
|||
|
||||
void EditFilterChatsListController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(std::move(_title));
|
||||
delegate()->peerListSetAboveWidget(prepareTypesList());
|
||||
if (!_community) {
|
||||
delegate()->peerListSetAboveWidget(prepareTypesList());
|
||||
}
|
||||
|
||||
const auto count = int(_peers.size());
|
||||
const auto rows = std::make_unique<std::optional<ExceptionRow>[]>(count);
|
||||
|
|
|
@ -75,6 +75,7 @@ private:
|
|||
Flags _options;
|
||||
Flags _selected;
|
||||
int _limit = 0;
|
||||
bool _community = false;
|
||||
|
||||
Fn<void(PeerListRowId)> _deselectOption;
|
||||
|
||||
|
|
871
Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
Normal file
871
Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
Normal file
|
@ -0,0 +1,871 @@
|
|||
/*
|
||||
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/filters/edit_filter_links.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // InviteLinkQrBox.
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/invite_link_buttons.h"
|
||||
#include "ui/controls/invite_link_label.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxLinkTitleLength = 32;
|
||||
|
||||
using InviteLinkData = Data::ChatFilterLink;
|
||||
class Row;
|
||||
|
||||
enum class Color {
|
||||
Permanent,
|
||||
|
||||
Count,
|
||||
};
|
||||
|
||||
struct InviteLinkAction {
|
||||
enum class Type {
|
||||
Copy,
|
||||
Share,
|
||||
Edit,
|
||||
Delete,
|
||||
};
|
||||
QString link;
|
||||
Type type = Type::Copy;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<QString> ErrorForSharing(
|
||||
not_null<History*> history) {
|
||||
const auto peer = history->peer;
|
||||
if (const auto user = peer->asUser()) { // langs
|
||||
return user->isBot()
|
||||
? u"you can't share chats with bots"_q
|
||||
: u"you can't share private chats"_q;
|
||||
} else if (const auto channel = history->peer->asChannel()) {
|
||||
if (!channel->canHaveInviteLink()) {
|
||||
return u"you can't invite others here"_q;
|
||||
}
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return u"you can't share this :("_q;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatFilterLinkBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Data::ChatFilterLink data) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto link = data.url;
|
||||
box->setTitle(tr::lng_group_invite_edit_title());
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
const auto addTitle = [&](
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<QString> text) {
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(text),
|
||||
st::settingsSubsectionTitle),
|
||||
(st::settingsSubsectionTitlePadding
|
||||
+ style::margins(0, st::settingsSectionSkip, 0, 0)));
|
||||
};
|
||||
const auto addDivider = [&](
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<QString> text,
|
||||
style::margins margins = style::margins()) {
|
||||
container->add(
|
||||
object_ptr<Ui::DividerLabel>(
|
||||
container,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(text),
|
||||
st::boxDividerLabel),
|
||||
st::settingsDividerLabelPadding),
|
||||
margins);
|
||||
};
|
||||
|
||||
struct State {
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
});
|
||||
|
||||
const auto labelField = container->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
st::defaultInputField,
|
||||
tr::lng_group_invite_label_header(),
|
||||
data.title),
|
||||
style::margins(
|
||||
st::settingsSubsectionTitlePadding.left(),
|
||||
st::settingsSectionSkip,
|
||||
st::settingsSubsectionTitlePadding.right(),
|
||||
st::settingsSectionSkip * 2));
|
||||
labelField->setMaxLength(kMaxLinkTitleLength);
|
||||
Settings::AddDivider(container);
|
||||
|
||||
const auto &saveLabel = link.isEmpty()
|
||||
? tr::lng_formatting_link_create
|
||||
: tr::lng_settings_save;
|
||||
box->addButton(saveLabel(), [=] {});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
class RowDelegate {
|
||||
public:
|
||||
virtual void rowUpdateRow(not_null<Row*> row) = 0;
|
||||
virtual void rowPaintIcon(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
Color color) = 0;
|
||||
};
|
||||
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
Row(not_null<RowDelegate*> delegate, const InviteLinkData &data);
|
||||
|
||||
void update(const InviteLinkData &data);
|
||||
|
||||
[[nodiscard]] InviteLinkData data() const;
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
private:
|
||||
const not_null<RowDelegate*> _delegate;
|
||||
InviteLinkData _data;
|
||||
QString _status;
|
||||
Color _color = Color::Permanent;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
|
||||
return XXH64(link.data(), link.size() * sizeof(ushort), 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 ComputeRowId(const InviteLinkData &data) {
|
||||
return ComputeRowId(data.url);
|
||||
}
|
||||
|
||||
[[nodiscard]] Color ComputeColor(const InviteLinkData &link) {
|
||||
return Color::Permanent;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ComputeStatus(const InviteLinkData &link) {
|
||||
return tr::lng_filters_chats_count(tr::now, lt_count, link.chats.size());
|
||||
}
|
||||
|
||||
Row::Row(not_null<RowDelegate*> delegate, const InviteLinkData &data)
|
||||
: PeerListRow(ComputeRowId(data))
|
||||
, _delegate(delegate)
|
||||
, _data(data)
|
||||
, _color(ComputeColor(data)) {
|
||||
setCustomStatus(ComputeStatus(data));
|
||||
}
|
||||
|
||||
void Row::update(const InviteLinkData &data) {
|
||||
_data = data;
|
||||
_color = ComputeColor(data);
|
||||
setCustomStatus(ComputeStatus(data));
|
||||
refreshName(st::inviteLinkList.item);
|
||||
_delegate->rowUpdateRow(this);
|
||||
}
|
||||
|
||||
InviteLinkData Row::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
if (!_data.title.isEmpty()) {
|
||||
return _data.title;
|
||||
}
|
||||
auto result = _data.url;
|
||||
return result.replace(
|
||||
u"https://"_q,
|
||||
QString()
|
||||
).replace(
|
||||
u"t.me/+"_q,
|
||||
QString()
|
||||
).replace(
|
||||
u"t.me/joinchat/"_q,
|
||||
QString()
|
||||
);
|
||||
}
|
||||
|
||||
QString Row::generateShortName() {
|
||||
return generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) {
|
||||
return [=](
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) {
|
||||
_delegate->rowPaintIcon(p, x, y, size, _color);
|
||||
};
|
||||
}
|
||||
|
||||
QSize Row::rightActionSize() const {
|
||||
return QSize(
|
||||
st::inviteLinkThreeDotsIcon.width(),
|
||||
st::inviteLinkThreeDotsIcon.height());
|
||||
}
|
||||
|
||||
QMargins Row::rightActionMargins() const {
|
||||
return QMargins(
|
||||
0,
|
||||
(st::inviteLinkList.item.height - rightActionSize().height()) / 2,
|
||||
st::inviteLinkThreeDotsSkip,
|
||||
0);
|
||||
}
|
||||
|
||||
void Row::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
(actionSelected
|
||||
? st::inviteLinkThreeDotsIconOver
|
||||
: st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth);
|
||||
}
|
||||
|
||||
class LinksController final
|
||||
: public PeerListController
|
||||
, public RowDelegate
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
LinksController(
|
||||
not_null<Window::SessionController*> window,
|
||||
rpl::producer<std::vector<InviteLinkData>> content,
|
||||
Fn<Data::ChatFilter()> currentFilter);
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
void rowUpdateRow(not_null<Row*> row) override;
|
||||
void rowPaintIcon(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
Color color) override;
|
||||
|
||||
private:
|
||||
void appendRow(const InviteLinkData &data);
|
||||
bool removeRow(const QString &link);
|
||||
|
||||
void rebuild(const std::vector<InviteLinkData> &rows);
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row);
|
||||
|
||||
const not_null<Window::SessionController*> _window;
|
||||
Fn<Data::ChatFilter()> _currentFilter;
|
||||
rpl::variable<std::vector<InviteLinkData>> _rows;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
std::array<QImage, int(Color::Count)> _icons;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class LinkController final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
LinkController(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter,
|
||||
InviteLinkData data);
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> hasChangesValue() const;
|
||||
|
||||
private:
|
||||
void setupAboveWidget();
|
||||
void addHeader(not_null<Ui::VerticalLayout*> container);
|
||||
void addLinkBlock(not_null<Ui::VerticalLayout*> container);
|
||||
|
||||
const not_null<Window::SessionController*> _window;
|
||||
InviteLinkData _data;
|
||||
|
||||
base::flat_set<not_null<History*>> _filterChats;
|
||||
base::flat_set<not_null<PeerData*>> _allowed;
|
||||
rpl::variable<int> _selected = 0;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
QString _link;
|
||||
|
||||
Ui::RpWidget *_headerWidget = nullptr;
|
||||
rpl::variable<int> _addedHeight;
|
||||
rpl::variable<bool> _hasChanges = false;
|
||||
|
||||
rpl::event_stream<> _showFinished;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
LinkController::LinkController(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter,
|
||||
InviteLinkData data)
|
||||
: _window(window)
|
||||
, _filterChats(filter.always()) {
|
||||
_data = std::move(data);
|
||||
_link = _data.url;
|
||||
}
|
||||
|
||||
void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
using namespace Settings;
|
||||
|
||||
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(
|
||||
container.get());
|
||||
const auto verticalLayout = container->add(
|
||||
object_ptr<Ui::VerticalLayout>(container.get()));
|
||||
|
||||
auto icon = CreateLottieIcon(
|
||||
verticalLayout,
|
||||
{
|
||||
.name = u"filters"_q,
|
||||
.sizeOverride = {
|
||||
st::settingsFilterIconSize,
|
||||
st::settingsFilterIconSize,
|
||||
},
|
||||
},
|
||||
st::settingsFilterIconPadding);
|
||||
_showFinished.events(
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, verticalLayout->lifetime());
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
verticalLayout,
|
||||
tr::lng_filters_about(), // langs
|
||||
st::settingsFilterDividerLabel)),
|
||||
st::settingsFilterDividerLabelPadding);
|
||||
|
||||
verticalLayout->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
divider->setGeometry(r);
|
||||
}, divider->lifetime());
|
||||
}
|
||||
|
||||
void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
using namespace Settings;
|
||||
|
||||
const auto link = _data.url;
|
||||
const auto weak = Ui::MakeWeak(container);
|
||||
const auto copyLink = crl::guard(weak, [=] {
|
||||
CopyInviteLink(delegate()->peerListToastParent(), link);
|
||||
});
|
||||
const auto shareLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(
|
||||
ShareInviteLinkBox(&_window->session(), link),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(
|
||||
InviteLinkQrBox(link),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
const auto editLink = crl::guard(weak, [=] {
|
||||
//delegate()->peerListShowBox(
|
||||
// EditLinkBox(_window, _data.current()),
|
||||
// Ui::LayerOption::KeepOther);
|
||||
});
|
||||
const auto deleteLink = crl::guard(weak, [=] {
|
||||
//delegate()->peerListShowBox(
|
||||
// DeleteLinkBox(_window, _data.current()),
|
||||
// Ui::LayerOption::KeepOther);
|
||||
});
|
||||
|
||||
const auto createMenu = [=] {
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
container,
|
||||
st::popupMenuWithIcons);
|
||||
result->addAction(
|
||||
tr::lng_group_invite_context_copy(tr::now),
|
||||
copyLink,
|
||||
&st::menuIconCopy);
|
||||
result->addAction(
|
||||
tr::lng_group_invite_context_share(tr::now),
|
||||
shareLink,
|
||||
&st::menuIconShare);
|
||||
result->addAction(
|
||||
tr::lng_group_invite_context_qr(tr::now),
|
||||
getLinkQr,
|
||||
&st::menuIconQrCode);
|
||||
result->addAction(
|
||||
tr::lng_group_invite_context_edit(tr::now),
|
||||
editLink,
|
||||
&st::menuIconEdit);
|
||||
result->addAction(
|
||||
tr::lng_group_invite_context_delete(tr::now),
|
||||
deleteLink,
|
||||
&st::menuIconDelete);
|
||||
return result;
|
||||
};
|
||||
|
||||
AddSubsectionTitle(container, tr::lng_manage_peer_link_invite());
|
||||
|
||||
const auto prefix = u"https://"_q;
|
||||
const auto label = container->lifetime().make_state<Ui::InviteLinkLabel>(
|
||||
container,
|
||||
rpl::single(link.startsWith(prefix)
|
||||
? link.mid(prefix.size())
|
||||
: link),
|
||||
createMenu);
|
||||
container->add(
|
||||
label->take(),
|
||||
st::inviteLinkFieldPadding);
|
||||
|
||||
label->clicks(
|
||||
) | rpl::start_with_next(copyLink, label->lifetime());
|
||||
|
||||
AddCopyShareLinkButtons(container, copyLink, shareLink);
|
||||
|
||||
AddSkip(container, st::inviteLinkJoinedRowPadding.bottom() * 2);
|
||||
|
||||
AddSkip(container);
|
||||
|
||||
AddDivider(container);
|
||||
}
|
||||
|
||||
void LinkController::prepare() {
|
||||
setupAboveWidget();
|
||||
auto selected = 0;
|
||||
for (const auto &history : _data.chats) {
|
||||
const auto peer = history->peer;
|
||||
_allowed.emplace(peer);
|
||||
auto row = std::make_unique<PeerListRow>(peer);
|
||||
const auto raw = row.get();
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListSetRowChecked(raw, true);
|
||||
++selected;
|
||||
}
|
||||
for (const auto &history : _filterChats) {
|
||||
if (delegate()->peerListFindRow(history->peer->id.value)) {
|
||||
continue;
|
||||
}
|
||||
const auto peer = history->peer;
|
||||
auto row = std::make_unique<PeerListRow>(peer);
|
||||
const auto raw = row.get();
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (const auto error = ErrorForSharing(history)) {
|
||||
raw->setCustomStatus(*error);
|
||||
} else {
|
||||
_allowed.emplace(peer);
|
||||
}
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
_selected = selected;
|
||||
}
|
||||
|
||||
void LinkController::rowClicked(not_null<PeerListRow*> row) {
|
||||
if (_allowed.contains(row->peer())) {
|
||||
const auto checked = row->checked();
|
||||
delegate()->peerListSetRowChecked(row, !checked);
|
||||
_selected = _selected.current() + (checked ? -1 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
void LinkController::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
void LinkController::setupAboveWidget() {
|
||||
using namespace Settings;
|
||||
|
||||
auto wrap = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = wrap.data();
|
||||
|
||||
addHeader(container);
|
||||
if (!_data.url.isEmpty()) {
|
||||
addLinkBlock(container);
|
||||
}
|
||||
|
||||
Settings::AddSubsectionTitle(
|
||||
container,
|
||||
rpl::single(u"3 chats selected"_q));
|
||||
|
||||
delegate()->peerListSetAboveWidget(std::move(wrap));
|
||||
}
|
||||
|
||||
Main::Session &LinkController::session() const {
|
||||
return _window->session();
|
||||
}
|
||||
|
||||
rpl::producer<bool> LinkController::hasChangesValue() const {
|
||||
return _hasChanges.value();
|
||||
}
|
||||
|
||||
LinksController::LinksController(
|
||||
not_null<Window::SessionController*> window,
|
||||
rpl::producer<std::vector<InviteLinkData>> content,
|
||||
Fn<Data::ChatFilter()> currentFilter)
|
||||
: _window(window)
|
||||
, _currentFilter(std::move(currentFilter))
|
||||
, _rows(std::move(content)) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (auto &image : _icons) {
|
||||
image = QImage();
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void LinksController::prepare() {
|
||||
_rows.value(
|
||||
) | rpl::start_with_next([=](const std::vector<InviteLinkData> &rows) {
|
||||
rebuild(rows);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void LinksController::rebuild(const std::vector<InviteLinkData> &rows) {
|
||||
auto i = 0;
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
while (i < rows.size()) {
|
||||
if (i < count) {
|
||||
const auto row = delegate()->peerListRowAt(i);
|
||||
static_cast<Row*>(row.get())->update(rows[i]);
|
||||
} else {
|
||||
appendRow(rows[i]);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
while (i < count) {
|
||||
delegate()->peerListRemoveRow(delegate()->peerListRowAt(i));
|
||||
--count;
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto link = static_cast<Row*>(row.get())->data();
|
||||
delegate()->peerListShowBox(
|
||||
ShowLinkBox(_window, _currentFilter(), link),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> LinksController::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
auto result = createRowContextMenu(parent, row);
|
||||
|
||||
if (result) {
|
||||
// First clear _menu value, so that we don't check row positions yet.
|
||||
base::take(_menu);
|
||||
|
||||
// Here unique_qptr is used like a shared pointer, where
|
||||
// not the last destroyed pointer destroys the object, but the first.
|
||||
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto real = static_cast<Row*>(row.get());
|
||||
const auto data = real->data();
|
||||
const auto link = data.url;
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
|
||||
//CopyInviteLink(delegate()->peerListToastParent(), link);
|
||||
}, &st::menuIconCopy);
|
||||
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
|
||||
//delegate()->peerListShowBox(
|
||||
// ShareInviteLinkBox(_peer, link),
|
||||
// Ui::LayerOption::KeepOther);
|
||||
}, &st::menuIconShare);
|
||||
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
||||
//delegate()->peerListShowBox(
|
||||
// InviteLinkQrBox(link),
|
||||
// Ui::LayerOption::KeepOther);
|
||||
}, &st::menuIconQrCode);
|
||||
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
||||
//delegate()->peerListShowBox(
|
||||
// EditLinkBox(_peer, data),
|
||||
// Ui::LayerOption::KeepOther);
|
||||
}, &st::menuIconEdit);
|
||||
result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] {
|
||||
//delegate()->peerListShowBox(
|
||||
// DeleteLinkBox(_peer, _admin, link),
|
||||
// Ui::LayerOption::KeepOther);
|
||||
}, &st::menuIconDelete);
|
||||
return result;
|
||||
}
|
||||
|
||||
Main::Session &LinksController::session() const {
|
||||
return _window->session();
|
||||
}
|
||||
|
||||
void LinksController::appendRow(const InviteLinkData &data) {
|
||||
delegate()->peerListAppendRow(std::make_unique<Row>(this, data));
|
||||
}
|
||||
|
||||
bool LinksController::removeRow(const QString &link) {
|
||||
if (const auto row = delegate()->peerListFindRow(ComputeRowId(link))) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LinksController::rowUpdateRow(not_null<Row*> row) {
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
|
||||
void LinksController::rowPaintIcon(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
Color color) {
|
||||
const auto skip = st::inviteLinkIconSkip;
|
||||
const auto inner = size - 2 * skip;
|
||||
const auto bg = [&] {
|
||||
switch (color) {
|
||||
case Color::Permanent: return &st::msgFile1Bg;
|
||||
}
|
||||
Unexpected("Color in LinksController::rowPaintIcon.");
|
||||
}();
|
||||
const auto stroke = st::inviteLinkIconStroke;
|
||||
auto &icon = _icons[int(color)];
|
||||
if (icon.isNull()) {
|
||||
icon = QImage(
|
||||
QSize(inner, inner) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
icon.fill(Qt::transparent);
|
||||
icon.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
|
||||
auto p = QPainter(&icon);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(*bg);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(QRect(0, 0, inner, inner));
|
||||
}
|
||||
st::inviteLinkIcon.paintInCenter(p, { 0, 0, inner, inner });
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
}
|
||||
|
||||
class LinkChatsController final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
LinkChatsController(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FilterId id,
|
||||
const InviteLinkData &data);
|
||||
~LinkChatsController();
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const FilterId _id = 0;
|
||||
InviteLinkData _data;
|
||||
|
||||
};
|
||||
|
||||
LinkChatsController::LinkChatsController(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FilterId id,
|
||||
const InviteLinkData &data)
|
||||
: _controller(controller)
|
||||
, _id(id)
|
||||
, _data(data) {
|
||||
}
|
||||
|
||||
LinkChatsController::~LinkChatsController() = default;
|
||||
|
||||
void LinkChatsController::prepare() {
|
||||
for (const auto &history : _data.chats) {
|
||||
delegate()->peerListAppendRow(
|
||||
std::make_unique<PeerListRow>(history->peer));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void LinkChatsController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
Main::Session &LinkChatsController::session() const {
|
||||
return _controller->session();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<not_null<PeerData*>> CollectFilterLinkChats(
|
||||
const Data::ChatFilter &filter) {
|
||||
return filter.always() | ranges::views::filter([](
|
||||
not_null<History*> history) {
|
||||
return !ErrorForSharing(history);
|
||||
}) | ranges::views::transform(&History::peer) | ranges::to_vector;
|
||||
}
|
||||
|
||||
bool GoodForExportFilterLink(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter) {
|
||||
using Flag = Data::ChatFilter::Flag;
|
||||
if (!filter.never().empty() || (filter.flags() & ~Flag::Community)) {
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = Window::Show(window).toastParent(),
|
||||
.text = { tr::lng_filters_link_cant(tr::now) },
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportFilterLink(
|
||||
FilterId id,
|
||||
const std::vector<not_null<PeerData*>> &peers,
|
||||
Fn<void(Data::ChatFilterLink)> done) {
|
||||
Expects(!peers.empty());
|
||||
|
||||
const auto front = peers.front();
|
||||
const auto session = &front->session();
|
||||
auto mtpPeers = peers | ranges::views::transform(
|
||||
[](not_null<PeerData*> peer) { return MTPInputPeer(peer->input); }
|
||||
) | ranges::to<QVector>();
|
||||
session->api().request(MTPcommunities_ExportCommunityInvite(
|
||||
MTP_inputCommunityDialogFilter(MTP_int(id)),
|
||||
MTP_string(),
|
||||
MTP_vector<MTPInputPeer>(std::move(mtpPeers))
|
||||
)).done([=](const MTPcommunities_ExportedCommunityInvite &result) {
|
||||
const auto &data = result.data();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag::f_filter),
|
||||
MTP_int(id),
|
||||
data.vfilter()));
|
||||
const auto link = session->data().chatsFilters().add(
|
||||
id,
|
||||
data.vinvite());
|
||||
done(link);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
done({ .id = id });
|
||||
}).send();
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ShowLinkBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter,
|
||||
const Data::ChatFilterLink &link) {
|
||||
auto controller = std::make_unique<LinkController>(window, filter, link);
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<Ui::BoxContent*> box) {
|
||||
box->setTitle(!link.title.isEmpty()
|
||||
? rpl::single(link.title)
|
||||
: tr::lng_manage_peer_link_invite());
|
||||
|
||||
raw->hasChangesValue(
|
||||
) | rpl::start_with_next([=](bool has) {
|
||||
box->clearButtons();
|
||||
if (has) {
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
} else {
|
||||
box->addButton(tr::lng_about_done(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
}, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
void SetupFilterLinks(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> window,
|
||||
rpl::producer<std::vector<Data::ChatFilterLink>> value,
|
||||
Fn<Data::ChatFilter()> currentFilter) {
|
||||
auto &lifetime = container->lifetime();
|
||||
const auto delegate = lifetime.make_state<PeerListContentDelegateShow>(
|
||||
std::make_shared<Window::Show>(window));
|
||||
const auto controller = lifetime.make_state<LinksController>(
|
||||
window,
|
||||
std::move(value),
|
||||
std::move(currentFilter));
|
||||
controller->setStyleOverrides(&st::inviteLinkList);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
}
|
47
Telegram/SourceFiles/boxes/filters/edit_filter_links.h
Normal file
47
Telegram/SourceFiles/boxes/filters/edit_filter_links.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
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 "base/object_ptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
class BoxContent;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
class ChatFilter;
|
||||
struct ChatFilterLink;
|
||||
} // namespace Data
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> CollectFilterLinkChats(
|
||||
const Data::ChatFilter &filter);
|
||||
[[nodiscard]] bool GoodForExportFilterLink(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter);
|
||||
|
||||
void ExportFilterLink(
|
||||
FilterId id,
|
||||
const std::vector<not_null<PeerData*>> &peers,
|
||||
Fn<void(Data::ChatFilterLink)> done);
|
||||
|
||||
object_ptr<Ui::BoxContent> ShowLinkBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ChatFilter &filter,
|
||||
const Data::ChatFilterLink &link);
|
||||
|
||||
void SetupFilterLinks(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> window,
|
||||
rpl::producer<std::vector<Data::ChatFilterLink>> value,
|
||||
Fn<Data::ChatFilter()> currentFilter);
|
|
@ -143,6 +143,10 @@ void PeerListBox::setAddedTopScrollSkip(int skip) {
|
|||
updateScrollSkips();
|
||||
}
|
||||
|
||||
void PeerListBox::showFinished() {
|
||||
_controller->showFinished();
|
||||
}
|
||||
|
||||
int PeerListBox::getTopScrollSkip() const {
|
||||
auto result = _addedTopScrollSkip;
|
||||
if (_select && !_select->isHidden()) {
|
||||
|
|
|
@ -447,6 +447,9 @@ public:
|
|||
|
||||
virtual void prepare() = 0;
|
||||
|
||||
virtual void showFinished() {
|
||||
}
|
||||
|
||||
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
|
||||
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
@ -1050,6 +1053,8 @@ public:
|
|||
|
||||
void setAddedTopScrollSkip(int skip);
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
|
|
@ -1128,6 +1128,12 @@ void CopyInviteLink(not_null<QWidget*> toastParent, const QString &link) {
|
|||
object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link) {
|
||||
return ShareInviteLinkBox(&peer->session(), link);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &link) {
|
||||
const auto sending = std::make_shared<bool>();
|
||||
const auto box = std::make_shared<QPointer<ShareBox>>();
|
||||
|
||||
|
@ -1187,7 +1193,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
|||
} else {
|
||||
comment.text = link;
|
||||
}
|
||||
auto &api = peer->session().api();
|
||||
auto &api = session->api();
|
||||
for (const auto thread : result) {
|
||||
auto message = Api::MessageToSend(
|
||||
Api::SendAction(thread, options));
|
||||
|
@ -1204,7 +1210,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
|||
return Data::CanSendTexts(thread);
|
||||
};
|
||||
auto object = Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &peer->session(),
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
|
|
|
@ -15,6 +15,10 @@ namespace Api {
|
|||
struct InviteLink;
|
||||
} // namespace Api
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
class Show;
|
||||
|
@ -38,6 +42,9 @@ void CopyInviteLink(not_null<QWidget*> toastParent, const QString &link);
|
|||
[[nodiscard]] object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link);
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &link);
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(const QString &link);
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> RevokeLinkBox(
|
||||
not_null<PeerData*> peer,
|
||||
|
|
|
@ -148,14 +148,19 @@ ChatFilter ChatFilter::FromTL(
|
|||
data.vid().v,
|
||||
qs(data.vtitle()),
|
||||
qs(data.vemoticon().value_or_empty()),
|
||||
(Flag::Community
|
||||
| (data.is_community_can_admin() ? Flag::Admin : Flag())),
|
||||
Flag::Community,
|
||||
std::move(list),
|
||||
std::move(pinned),
|
||||
{});
|
||||
});
|
||||
}
|
||||
|
||||
ChatFilter ChatFilter::withId(FilterId id) const {
|
||||
auto result = *this;
|
||||
result._id = id;
|
||||
return result;
|
||||
}
|
||||
|
||||
MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
||||
auto always = _always;
|
||||
auto pinned = QVector<MTPInputPeer>();
|
||||
|
@ -171,10 +176,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
|||
}
|
||||
if (_flags & Flag::Community) {
|
||||
using TLFlag = MTPDdialogFilterCommunity::Flag;
|
||||
const auto flags = TLFlag::f_emoticon
|
||||
| ((_flags & Flag::Admin)
|
||||
? TLFlag::f_community_can_admin
|
||||
: TLFlag(0));
|
||||
const auto flags = TLFlag::f_emoticon;
|
||||
return MTP_dialogFilterCommunity(
|
||||
MTP_flags(flags),
|
||||
MTP_int(replaceId ? replaceId : _id),
|
||||
|
@ -226,6 +228,10 @@ ChatFilter::Flags ChatFilter::flags() const {
|
|||
return _flags;
|
||||
}
|
||||
|
||||
bool ChatFilter::community() const {
|
||||
return _flags & Flag::Community;
|
||||
}
|
||||
|
||||
const base::flat_set<not_null<History*>> &ChatFilter::always() const {
|
||||
return _always;
|
||||
}
|
||||
|
@ -392,6 +398,93 @@ void ChatFilters::apply(const MTPUpdate &update) {
|
|||
});
|
||||
}
|
||||
|
||||
ChatFilterLink ChatFilters::add(
|
||||
FilterId id,
|
||||
const MTPExportedCommunityInvite &update) {
|
||||
const auto i = ranges::find(_list, id, &ChatFilter::id);
|
||||
if (i == end(_list) || !i->community()) {
|
||||
LOG(("Api Error: "
|
||||
"Attempt to add community link to a non-community filter: %1"
|
||||
).arg(id));
|
||||
return {};
|
||||
}
|
||||
auto &links = _communityLinks[id];
|
||||
const auto &data = update.data();
|
||||
const auto url = qs(data.vurl());
|
||||
const auto title = qs(data.vtitle());
|
||||
auto chats = data.vpeers().v | ranges::views::transform([&](
|
||||
const MTPPeer &peer) {
|
||||
return _owner->history(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto j = ranges::find(links, url, &ChatFilterLink::url);
|
||||
if (j != end(links)) {
|
||||
if (j->title != title || j->chats != chats) {
|
||||
j->title = title;
|
||||
j->chats = std::move(chats);
|
||||
_communityLinksUpdated.fire_copy(id);
|
||||
}
|
||||
return *j;
|
||||
}
|
||||
links.push_back({
|
||||
.id = id,
|
||||
.url = url,
|
||||
.title = title,
|
||||
.chats = std::move(chats),
|
||||
});
|
||||
_communityLinksUpdated.fire_copy(id);
|
||||
return links.back();
|
||||
}
|
||||
|
||||
void ChatFilters::edit(
|
||||
FilterId id,
|
||||
const QString &url,
|
||||
const QString &title) {
|
||||
auto &links = _communityLinks[id];
|
||||
const auto i = ranges::find(links, url, &ChatFilterLink::url);
|
||||
if (i != end(links)) {
|
||||
i->title = title;
|
||||
_communityLinksUpdated.fire_copy(id);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatFilters::remove(FilterId id, const QString &url) {
|
||||
auto &links = _communityLinks[id];
|
||||
const auto i = ranges::find(links, url, &ChatFilterLink::url);
|
||||
if (i != end(links)) {
|
||||
links.erase(i);
|
||||
_communityLinksUpdated.fire_copy(id);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<ChatFilterLink>> ChatFilters::communityLinks(
|
||||
FilterId id) const {
|
||||
return _communityLinksUpdated.events_starting_with_copy(
|
||||
id
|
||||
) | rpl::filter(rpl::mappers::_1 == id) | rpl::map([=] {
|
||||
const auto i = _communityLinks.find(id);
|
||||
return (i != end(_communityLinks))
|
||||
? i->second
|
||||
: std::vector<ChatFilterLink>();
|
||||
});
|
||||
}
|
||||
|
||||
void ChatFilters::reloadCommunityLinks(FilterId id) {
|
||||
const auto api = &_owner->session().api();
|
||||
api->request(_linksRequestId).cancel();
|
||||
_linksRequestId = api->request(MTPcommunities_GetExportedInvites(
|
||||
MTP_inputCommunityDialogFilter(MTP_int(id))
|
||||
)).done([=](const MTPcommunities_ExportedInvites &result) {
|
||||
const auto &data = result.data();
|
||||
_owner->processUsers(data.vusers());
|
||||
_owner->processChats(data.vchats());
|
||||
_communityLinks[id].clear();
|
||||
for (const auto &link : data.vinvites().v) {
|
||||
add(id, link);
|
||||
}
|
||||
_communityLinksUpdated.fire_copy(id);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ChatFilters::set(ChatFilter filter) {
|
||||
if (!filter.id()) {
|
||||
return;
|
||||
|
|
|
@ -33,7 +33,6 @@ public:
|
|||
NoArchived = (1 << 7),
|
||||
|
||||
Community = (1 << 8),
|
||||
Admin = (1 << 9),
|
||||
};
|
||||
friend constexpr inline bool is_flag_type(Flag) { return true; };
|
||||
using Flags = base::flags<Flag>;
|
||||
|
@ -48,6 +47,8 @@ public:
|
|||
std::vector<not_null<History*>> pinned,
|
||||
base::flat_set<not_null<History*>> never);
|
||||
|
||||
[[nodiscard]] ChatFilter withId(FilterId id) const;
|
||||
|
||||
[[nodiscard]] static ChatFilter FromTL(
|
||||
const MTPDialogFilter &data,
|
||||
not_null<Session*> owner);
|
||||
|
@ -57,7 +58,7 @@ public:
|
|||
[[nodiscard]] QString title() const;
|
||||
[[nodiscard]] QString iconEmoji() const;
|
||||
[[nodiscard]] Flags flags() const;
|
||||
[[nodiscard]] bool admin() const;
|
||||
[[nodiscard]] bool community() const;
|
||||
[[nodiscard]] const base::flat_set<not_null<History*>> &always() const;
|
||||
[[nodiscard]] const std::vector<not_null<History*>> &pinned() const;
|
||||
[[nodiscard]] const base::flat_set<not_null<History*>> &never() const;
|
||||
|
@ -87,6 +88,17 @@ inline bool operator!=(const ChatFilter &a, const ChatFilter &b) {
|
|||
return !(a == b);
|
||||
}
|
||||
|
||||
struct ChatFilterLink {
|
||||
FilterId id = 0;
|
||||
QString url;
|
||||
QString title;
|
||||
std::vector<not_null<History*>> chats;
|
||||
|
||||
friend inline bool operator==(
|
||||
const ChatFilterLink &a,
|
||||
const ChatFilterLink &b) = default;
|
||||
};
|
||||
|
||||
struct SuggestedFilter {
|
||||
ChatFilter filter;
|
||||
QString description;
|
||||
|
@ -134,6 +146,18 @@ public:
|
|||
-> const std::vector<SuggestedFilter> &;
|
||||
[[nodiscard]] rpl::producer<> suggestedUpdated() const;
|
||||
|
||||
ChatFilterLink add(
|
||||
FilterId id,
|
||||
const MTPExportedCommunityInvite &update);
|
||||
void edit(
|
||||
FilterId id,
|
||||
const QString &url,
|
||||
const QString &title);
|
||||
void remove(FilterId id, const QString &url);
|
||||
rpl::producer<std::vector<ChatFilterLink>> communityLinks(
|
||||
FilterId id) const;
|
||||
void reloadCommunityLinks(FilterId id);
|
||||
|
||||
private:
|
||||
void load(bool force);
|
||||
void received(const QVector<MTPDialogFilter> &list);
|
||||
|
@ -160,6 +184,10 @@ private:
|
|||
std::deque<FilterId> _exceptionsToLoad;
|
||||
mtpRequestId _exceptionsLoadRequestId = 0;
|
||||
|
||||
base::flat_map<FilterId, std::vector<ChatFilterLink>> _communityLinks;
|
||||
rpl::event_stream<FilterId> _communityLinksUpdated;
|
||||
mtpRequestId _linksRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -133,6 +133,7 @@ settingsTTLChatsOn: icon {{ "settings/ttl/autodelete_on", windowActiveTextFg }};
|
|||
|
||||
settingsIconAdd: icon {{ "settings/add", windowFgActive }};
|
||||
settingsIconRemove: icon {{ "settings/remove", windowFgActive }};
|
||||
settingsFolderShareIcon: icon {{ "settings/folder_links", lightButtonFg }};
|
||||
|
||||
settingsCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
textPosition: point(15px, 1px);
|
||||
|
|
|
@ -334,16 +334,24 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
AddSkip(container, st::settingsSectionSkip);
|
||||
AddSubsectionTitle(container, tr::lng_filters_subtitle());
|
||||
|
||||
const auto rows = lifetime.make_state<std::vector<FilterRow>>();
|
||||
const auto rowsCount = lifetime.make_state<rpl::variable<int>>();
|
||||
struct State {
|
||||
std::vector<FilterRow> rows;
|
||||
rpl::variable<int> count;
|
||||
rpl::variable<int> suggested;
|
||||
Fn<void(const FilterRowButton*, Fn<void(Data::ChatFilter)>)> save;
|
||||
};
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto find = [=](not_null<FilterRowButton*> button) {
|
||||
const auto i = ranges::find(*rows, button, &FilterRow::button);
|
||||
Assert(i != end(*rows));
|
||||
const auto i = ranges::find(state->rows, button, &FilterRow::button);
|
||||
Assert(i != end(state->rows));
|
||||
return &*i;
|
||||
};
|
||||
const auto showLimitReached = [=] {
|
||||
const auto removed = ranges::count_if(*rows, &FilterRow::removed);
|
||||
if (rows->size() < limit() + removed) {
|
||||
const auto removed = ranges::count_if(
|
||||
state->rows,
|
||||
&FilterRow::removed);
|
||||
if (state->rows.size() < limit() + removed) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Box(FiltersLimitBox, session));
|
||||
|
@ -376,14 +384,23 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
find(button)->filter = result;
|
||||
button->updateData(result);
|
||||
};
|
||||
const auto saveAnd = [=](
|
||||
const Data::ChatFilter &data,
|
||||
Fn<void(Data::ChatFilter)> next) {
|
||||
const auto found = find(button);
|
||||
found->filter = data;
|
||||
button->updateData(data);
|
||||
state->save(button, next);
|
||||
};
|
||||
controller->window().show(Box(
|
||||
EditFilterBox,
|
||||
controller,
|
||||
found->filter,
|
||||
crl::guard(button, doneCallback)));
|
||||
crl::guard(button, doneCallback),
|
||||
crl::guard(button, saveAnd)));
|
||||
});
|
||||
rows->push_back({ button, filter });
|
||||
*rowsCount = rows->size();
|
||||
state->rows.push_back({ button, filter });
|
||||
state->count = state->rows.size();
|
||||
|
||||
const auto filters = &controller->session().data().chatsFilters();
|
||||
const auto id = filter.id();
|
||||
|
@ -418,6 +435,8 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
|
||||
wrap->resizeToWidth(container->width());
|
||||
|
||||
return button;
|
||||
};
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
for (const auto &filter : list) {
|
||||
|
@ -438,11 +457,17 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
const auto doneCallback = [=](const Data::ChatFilter &result) {
|
||||
addFilter(result);
|
||||
};
|
||||
const auto saveAnd = [=](
|
||||
const Data::ChatFilter &data,
|
||||
Fn<void(Data::ChatFilter)> next) {
|
||||
state->save(addFilter(data), next);
|
||||
};
|
||||
controller->window().show(Box(
|
||||
EditFilterBox,
|
||||
controller,
|
||||
Data::ChatFilter(),
|
||||
crl::guard(container, doneCallback)));
|
||||
crl::guard(container, doneCallback),
|
||||
crl::guard(container, saveAnd)));
|
||||
});
|
||||
AddSkip(container);
|
||||
const auto nonEmptyAbout = container->add(
|
||||
|
@ -455,7 +480,6 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
AddSkip(aboutRows);
|
||||
AddSubsectionTitle(aboutRows, tr::lng_filters_recommended());
|
||||
|
||||
const auto suggested = lifetime.make_state<rpl::variable<int>>();
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
session->data().chatsFilters().suggestedUpdated()
|
||||
) | rpl::map([=] {
|
||||
|
@ -468,10 +492,10 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
const std::vector<Data::SuggestedFilter> &suggestions) {
|
||||
for (const auto &suggestion : suggestions) {
|
||||
const auto &filter = suggestion.filter;
|
||||
if (ranges::contains(*rows, filter, &FilterRow::filter)) {
|
||||
if (ranges::contains(state->rows, filter, &FilterRow::filter)) {
|
||||
continue;
|
||||
}
|
||||
*suggested = suggested->current() + 1;
|
||||
state->suggested = state->suggested.current() + 1;
|
||||
const auto button = aboutRows->add(object_ptr<FilterRowButton>(
|
||||
aboutRows,
|
||||
filter,
|
||||
|
@ -482,7 +506,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
return;
|
||||
}
|
||||
addFilter(filter);
|
||||
*suggested = suggested->current() - 1;
|
||||
state->suggested = state->suggested.current() - 1;
|
||||
delete button;
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
@ -491,8 +515,8 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
}, aboutRows->lifetime());
|
||||
|
||||
auto showSuggestions = rpl::combine(
|
||||
suggested->value(),
|
||||
rowsCount->value(),
|
||||
state->suggested.value(),
|
||||
state->count.value(),
|
||||
Data::AmPremiumValue(session)
|
||||
) | rpl::map([limit](int suggested, int count, bool) {
|
||||
return suggested > 0 && count < limit();
|
||||
|
@ -511,7 +535,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
return localId;
|
||||
};
|
||||
auto result = base::flat_map<not_null<FilterRowButton*>, FilterId>();
|
||||
for (auto &row : *rows) {
|
||||
for (auto &row : state->rows) {
|
||||
const auto id = row.filter.id();
|
||||
if (row.removed) {
|
||||
continue;
|
||||
|
@ -523,9 +547,13 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
return result;
|
||||
};
|
||||
|
||||
return [=] {
|
||||
state->save = [=](
|
||||
const FilterRowButton *single,
|
||||
Fn<void(Data::ChatFilter)> next) {
|
||||
auto ids = prepareGoodIdsForNewFilters();
|
||||
|
||||
auto updated = Data::ChatFilter();
|
||||
|
||||
auto order = std::vector<FilterId>();
|
||||
auto updates = std::vector<MTPUpdate>();
|
||||
auto addRequests = std::vector<MTPmessages_UpdateDialogFilter>();
|
||||
|
@ -533,8 +561,11 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
|
||||
auto &realFilters = session->data().chatsFilters();
|
||||
const auto &list = realFilters.list();
|
||||
order.reserve(rows->size());
|
||||
for (const auto &row : *rows) {
|
||||
order.reserve(state->rows.size());
|
||||
for (auto &row : state->rows) {
|
||||
if (row.button.get() == single) {
|
||||
updated = row.filter;
|
||||
}
|
||||
const auto id = row.filter.id();
|
||||
const auto removed = row.removed;
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
|
@ -545,6 +576,13 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
continue;
|
||||
}
|
||||
const auto newId = ids.take(row.button).value_or(id);
|
||||
if (newId != id) {
|
||||
row.filter = row.filter.withId(newId);
|
||||
row.button->updateData(row.filter);
|
||||
if (row.button.get() == single) {
|
||||
updated = row.filter;
|
||||
}
|
||||
}
|
||||
const auto tl = removed
|
||||
? MTPDialogFilter()
|
||||
: row.filter.tl(newId);
|
||||
|
@ -582,6 +620,8 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
crl::on_main(session, [
|
||||
session,
|
||||
next,
|
||||
updated,
|
||||
order = std::move(order),
|
||||
updates = std::move(updates),
|
||||
addRequests = std::move(addRequests),
|
||||
|
@ -604,8 +644,15 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
if (!order.empty() && !addRequests.empty()) {
|
||||
filters->saveOrder(order, previousId);
|
||||
}
|
||||
if (next) {
|
||||
Assert(updated.id() != 0);
|
||||
next(updated);
|
||||
}
|
||||
});
|
||||
};
|
||||
return [copy = state->save] {
|
||||
copy(nullptr, nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
void SetupTopContent(
|
||||
|
|
Loading…
Add table
Reference in a new issue