Allow adding chats to filter exceptions.

This commit is contained in:
John Preston 2020-03-13 15:07:27 +04:00
parent d5bd9fa54d
commit 13fe0b6272
8 changed files with 624 additions and 376 deletions

View File

@ -146,6 +146,12 @@ PRIVATE
api/api_single_message_search.h
api/api_text_entities.cpp
api/api_text_entities.h
boxes/filters/edit_filter_box.cpp
boxes/filters/edit_filter_box.h
boxes/filters/edit_filter_chats_list.cpp
boxes/filters/edit_filter_chats_list.h
boxes/filters/manage_filters_box.cpp
boxes/filters/manage_filters_box.h
boxes/peers/add_participants_box.cpp
boxes/peers/add_participants_box.h
boxes/peers/edit_contact_box.cpp
@ -204,8 +210,6 @@ PRIVATE
boxes/language_box.h
boxes/local_storage_box.cpp
boxes/local_storage_box.h
boxes/manage_filters_box.cpp
boxes/manage_filters_box.h
boxes/mute_settings_box.cpp
boxes/mute_settings_box.h
boxes/peer_list_box.cpp

View File

@ -0,0 +1,466 @@
/*
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_box.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "data/data_chat_filters.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "settings/settings_common.h"
#include "lang/lang_keys.h"
#include "history/history.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
namespace {
using namespace Settings;
constexpr auto kMaxFilterTitleLength = 20;
using Flag = Data::ChatFilter::Flag;
using Flags = Data::ChatFilter::Flags;
using ExceptionPeersRef = const base::flat_set<not_null<History*>> &;
using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const;
constexpr auto kAllTypes = {
Flag::Contacts,
Flag::NonContacts,
Flag::Groups,
Flag::Channels,
Flag::Bots,
Flag::NoMuted,
Flag::NoArchived,
Flag::NoRead
};
class FilterChatsPreview final : public Ui::RpWidget {
public:
FilterChatsPreview(
not_null<QWidget*> parent,
Flags flags,
const base::flat_set<not_null<History*>> &peers);
[[nodiscard]] rpl::producer<Flag> flagRemoved() const;
[[nodiscard]] rpl::producer<not_null<History*>> peerRemoved() const;
void updateData(
Flags flags,
const base::flat_set<not_null<History*>> &peers);
int resizeGetHeight(int newWidth) override;
private:
using Button = base::unique_qptr<Ui::IconButton>;
struct FlagButton {
Flag flag = Flag();
Button button;
};
struct PeerButton {
not_null<History*> history;
Button button;
};
void paintEvent(QPaintEvent *e) override;
void setup(
Flags flags,
const base::flat_set<not_null<History*>> &peers);
void refresh();
void removeFlag(Flag flag);
void removePeer(not_null<History*> history);
void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const;
std::vector<FlagButton> _removeFlag;
std::vector<PeerButton> _removePeer;
rpl::event_stream<Flag> _flagRemoved;
rpl::event_stream<not_null<History*>> _peerRemoved;
};
not_null<FilterChatsPreview*> SetupChatsPreview(
not_null<Ui::VerticalLayout*> content,
not_null<Data::ChatFilter*> data,
Flags flags,
ExceptionPeersGetter peers) {
const auto preview = content->add(object_ptr<FilterChatsPreview>(
content,
data->flags() & flags,
(data->*peers)()));
preview->flagRemoved(
) | rpl::start_with_next([=](Flag flag) {
*data = Data::ChatFilter(
data->id(),
data->title(),
(data->flags() & ~flag),
data->always(),
data->never());
}, preview->lifetime());
preview->peerRemoved(
) | rpl::start_with_next([=](not_null<History*> history) {
auto always = data->always();
auto never = data->never();
always.remove(history);
never.remove(history);
*data = Data::ChatFilter(
data->id(),
data->title(),
data->flags(),
std::move(always),
std::move(never));
}, preview->lifetime());
return preview;
}
FilterChatsPreview::FilterChatsPreview(
not_null<QWidget*> parent,
Flags flags,
const base::flat_set<not_null<History*>> &peers)
: RpWidget(parent) {
updateData(flags, peers);
}
void FilterChatsPreview::refresh() {
resizeToWidth(width());
}
void FilterChatsPreview::updateData(
Flags flags,
const base::flat_set<not_null<History*>> &peers) {
_removeFlag.clear();
_removePeer.clear();
const auto makeButton = [&](Fn<void()> handler) {
auto result = base::make_unique_q<Ui::IconButton>(
this,
st::windowFilterSmallRemove);
result->setClickedCallback(std::move(handler));
return result;
};
for (const auto flag : kAllTypes) {
if (flags & flag) {
_removeFlag.push_back({
flag,
makeButton([=] { removeFlag(flag); }) });
}
}
for (const auto history : peers) {
_removePeer.push_back({
history,
makeButton([=] { removePeer(history); }) });
}
refresh();
}
int FilterChatsPreview::resizeGetHeight(int newWidth) {
const auto right = st::windowFilterSmallRemoveRight;
const auto add = (st::windowFilterSmallItem.height
- st::windowFilterSmallRemove.height) / 2;
auto top = 0;
const auto moveNextButton = [&](not_null<Ui::IconButton*> button) {
button->moveToRight(right, top + add, newWidth);
top += st::windowFilterSmallItem.height;
};
for (const auto &[flag, button] : _removeFlag) {
moveNextButton(button.get());
}
for (const auto &[history, button] : _removePeer) {
moveNextButton(button.get());
}
return top;
}
[[nodiscard]] QString TypeName(Flag flag) {
switch (flag) {
case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now);
case Flag::NonContacts:
return tr::lng_filters_type_non_contacts(tr::now);
case Flag::Groups: return tr::lng_filters_type_groups(tr::now);
case Flag::Channels: return tr::lng_filters_type_channels(tr::now);
case Flag::Bots: return tr::lng_filters_type_bots(tr::now);
case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now);
case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now);
case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now);
}
Unexpected("Flag in TypeName.");
}
void FilterChatsPreview::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
auto top = 0;
const auto &st = st::windowFilterSmallItem;
const auto iconLeft = st.photoPosition.x();
const auto iconTop = st.photoPosition.y();
const auto nameLeft = st.namePosition.x();
p.setFont(st::windowFilterSmallItem.nameStyle.font);
const auto nameTop = st.namePosition.y();
for (const auto &[flag, button] : _removeFlag) {
paintFlagIcon(p, iconLeft, top + iconTop, flag);
p.setPen(st::contactsNameFg);
p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag));
top += st.height;
}
for (const auto &[history, button] : _removePeer) {
history->peer->paintUserpicLeft(
p,
iconLeft,
top + iconTop,
width(),
st.photoSize);
p.setPen(st::contactsNameFg);
history->peer->nameText().drawLeftElided(
p,
nameLeft,
top + nameTop,
button->x() - nameLeft,
width());
top += st.height;
}
}
void FilterChatsPreview::paintFlagIcon(
QPainter &p,
int left,
int top,
Flag flag) const {
const auto &color = [&]() -> const style::color& {
switch (flag) {
case Flag::Contacts: return st::historyPeer4UserpicBg;
case Flag::NonContacts: return st::historyPeer7UserpicBg;
case Flag::Groups: return st::historyPeer2UserpicBg;
case Flag::Channels: return st::historyPeer1UserpicBg;
case Flag::Bots: return st::historyPeer6UserpicBg;
case Flag::NoMuted: return st::historyPeer6UserpicBg;
case Flag::NoArchived: return st::historyPeer4UserpicBg;
case Flag::NoRead: return st::historyPeer7UserpicBg;
}
Unexpected("Flag in color paintFlagIcon.");
}();
const auto &icon = [&]() -> const style::icon& {
switch (flag) {
case Flag::Contacts: return st::windowFilterTypeContacts;
case Flag::NonContacts: return st::windowFilterTypeNonContacts;
case Flag::Groups: return st::windowFilterTypeGroups;
case Flag::Channels: return st::windowFilterTypeChannels;
case Flag::Bots: return st::windowFilterTypeBots;
case Flag::NoMuted: return st::windowFilterTypeNoMuted;
case Flag::NoArchived: return st::windowFilterTypeNoArchived;
case Flag::NoRead: return st::windowFilterTypeNoRead;
}
Unexpected("Flag in icon paintFlagIcon.");
}();
const auto size = st::windowFilterSmallItem.photoSize;
const auto rect = QRect(left, top, size, size);
auto hq = PainterHighQualityEnabler(p);
p.setBrush(color->b);
p.setPen(Qt::NoPen);
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}
void FilterChatsPreview::removeFlag(Flag flag) {
const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag);
Assert(i != end(_removeFlag));
_removeFlag.erase(i);
refresh();
_flagRemoved.fire_copy(flag);
}
void FilterChatsPreview::removePeer(not_null<History*> history) {
const auto i = ranges::find(_removePeer, history, &PeerButton::history);
Assert(i != end(_removePeer));
_removePeer.erase(i);
refresh();
_peerRemoved.fire_copy(history);
}
rpl::producer<Flag> FilterChatsPreview::flagRemoved() const {
return _flagRemoved.events();
}
rpl::producer<not_null<History*>> FilterChatsPreview::peerRemoved() const {
return _peerRemoved.events();
}
void EditExceptions(
not_null<Window::SessionController*> window,
not_null<QObject*> context,
Flags options,
not_null<Data::ChatFilter*> data,
Fn<void()> refresh) {
const auto include = (options & Flag::Contacts) != Flags(0);
const auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), crl::guard(context, [=] {
const auto peers = box->peerListCollectSelectedRows();
auto &&histories = ranges::view::all(
peers
) | ranges::view::transform([=](not_null<PeerData*> peer) {
return window->session().data().history(peer);
});
auto changed = base::flat_set<not_null<History*>>{
histories.begin(),
histories.end()
};
auto removeFrom = include ? data->never() : data->always();
for (const auto &history : changed) {
removeFrom.remove(history);
}
*data = Data::ChatFilter(
data->id(),
data->title(),
data->flags(),
include ? std::move(changed) : std::move(removeFrom),
include ? std::move(removeFrom) : std::move(changed));
refresh();
box->closeBox();
}));
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
};
window->window().show(
Box<PeerListBox>(
std::make_unique<EditFilterChatsListController>(
window,
(include
? tr::lng_filters_include_title()
: tr::lng_filters_exclude_title()),
options,
data->flags() & options,
include ? data->always() : data->never()),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}
} // namespace
void EditFilterBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window,
const Data::ChatFilter &filter,
Fn<void(const Data::ChatFilter &)> doneCallback) {
const auto creating = filter.title().isEmpty();
box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
const auto content = box->verticalLayout();
const auto name = content->add(
object_ptr<Ui::InputField>(
box,
st::defaultInputField,
tr::lng_filters_new_name(),
filter.title()),
st::markdownLinkFieldPadding);
name->setMaxLength(kMaxFilterTitleLength);
const auto data = box->lifetime().make_state<Data::ChatFilter>(filter);
constexpr auto kTypes = Flag::Contacts
| Flag::NonContacts
| Flag::Groups
| Flag::Channels
| Flag::Bots;
constexpr auto kExcludeTypes = Flag::NoMuted
| Flag::NoArchived
| Flag::NoRead;
box->setFocusCallback([=] {
name->setFocusFast();
});
AddSkip(content);
AddDivider(content);
AddSkip(content);
AddSubsectionTitle(content, tr::lng_filters_include());
const auto include = SetupChatsPreview(
content,
data,
kTypes,
&Data::ChatFilter::always);
const auto includeAdd = AddButton(
content,
tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
st::settingsUpdate);
AddSkip(content);
AddDividerText(content, tr::lng_filters_include_about());
AddSkip(content);
AddSubsectionTitle(content, tr::lng_filters_exclude());
const auto exclude = SetupChatsPreview(
content,
data,
kExcludeTypes,
&Data::ChatFilter::never);
const auto excludeAdd = AddButton(
content,
tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
st::settingsUpdate);
AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_filters_exclude_about(),
st::boxDividerLabel),
st::settingsDividerLabelPadding);
const auto refreshPreviews = [=] {
include->updateData(data->flags() & kTypes, data->always());
exclude->updateData(data->flags() & kExcludeTypes, data->never());
};
includeAdd->setClickedCallback([=] {
EditExceptions(window, box, kTypes, data, refreshPreviews);
});
excludeAdd->setClickedCallback([=] {
EditExceptions(window, box, kExcludeTypes, data, refreshPreviews);
});
const auto save = [=] {
const auto title = name->getLastText().trimmed();
if (title.isEmpty()) {
name->showError();
return;
} else if (!(data->flags() & kTypes) && data->always().empty()) {
window->window().showToast(tr::lng_filters_empty(tr::now));
return;
} else if ((data->flags() == (kTypes | Flag::NoArchived))
&& data->always().empty()
&& data->never().empty()) {
window->window().showToast(tr::lng_filters_default(tr::now));
return;
}
const auto result = Data::ChatFilter(
data->id(),
title,
data->flags(),
data->always(),
data->never());
box->closeBox();
doneCallback(result);
};
box->addButton(
creating ? tr::lng_filters_create_button() : tr::lng_settings_save(),
save);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}

View File

@ -0,0 +1,26 @@
/*
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
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
namespace Data {
class ChatFilter;
} // namespace Data
void EditFilterBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window,
const Data::ChatFilter &filter,
Fn<void(const Data::ChatFilter &)> doneCallback);

View File

@ -0,0 +1,66 @@
/*
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_chats_list.h"
#include "history/history.h"
#include "window/window_session_controller.h"
#include "lang/lang_keys.h"
namespace {
constexpr auto kMaxExceptions = 100;
} // namespace
EditFilterChatsListController::EditFilterChatsListController(
not_null<Window::SessionNavigation*> navigation,
rpl::producer<QString> title,
Flags options,
Flags selected,
const base::flat_set<not_null<History*>> &peers)
: ChatsListBoxController(navigation)
, _navigation(navigation)
, _title(std::move(title))
, _peers(peers) {
}
Main::Session &EditFilterChatsListController::session() const {
return _navigation->session();
}
void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {
const auto count = delegate()->peerListSelectedRowsCount();
if (count < kMaxExceptions || row->checked()) {
delegate()->peerListSetRowChecked(row, !row->checked());
updateTitle();
}
}
void EditFilterChatsListController::itemDeselectedHook(
not_null<PeerData*> peer) {
updateTitle();
}
void EditFilterChatsListController::prepareViewHook() {
delegate()->peerListSetTitle(std::move(_title));
delegate()->peerListAddSelectedRows(
_peers | ranges::view::transform(&History::peer));
}
auto EditFilterChatsListController::createRow(not_null<History*> history)
-> std::unique_ptr<Row> {
return std::make_unique<Row>(history);
}
void EditFilterChatsListController::updateTitle() {
const auto count = delegate()->peerListSelectedRowsCount();
const auto additional = qsl("%1 / %2").arg(count).arg(kMaxExceptions);
delegate()->peerListSetTitle(tr::lng_profile_add_participant());
delegate()->peerListSetAdditionalTitle(rpl::single(additional));
}

View File

@ -0,0 +1,55 @@
/*
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 "boxes/peer_list_controllers.h"
#include "data/data_chat_filters.h"
class History;
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
namespace Main {
class Session;
} // namespace Main
class EditFilterChatsListController final : public ChatsListBoxController {
public:
using Flag = Data::ChatFilter::Flag;
using Flags = Data::ChatFilter::Flags;
EditFilterChatsListController(
not_null<Window::SessionNavigation*> navigation,
rpl::producer<QString> title,
Flags options,
Flags selected,
const base::flat_set<not_null<History*>> &peers);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
void itemDeselectedHook(not_null<PeerData*> peer) override;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
private:
void updateTitle();
const not_null<Window::SessionNavigation*> _navigation;
rpl::producer<QString> _title;
base::flat_set<not_null<History*>> _peers;
};

View File

@ -5,10 +5,10 @@ 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/manage_filters_box.h"
#include "boxes/filters/manage_filters_box.h"
#include "boxes/filters/edit_filter_box.h"
#include "data/data_session.h"
#include "data/data_chat_filters.h"
#include "data/data_folder.h"
#include "data/data_peer.h"
#include "history/history.h"
@ -35,66 +35,11 @@ namespace {
constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000);
constexpr auto kFiltersLimit = 10;
constexpr auto kMaxFilterTitleLength = 20;
using namespace Settings;
using Flag = Data::ChatFilter::Flag;
using Flags = Data::ChatFilter::Flags;
using ExceptionPeersRef = const base::flat_set<not_null<History*>> &;
using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const;
constexpr auto kAllTypes = {
Flag::Contacts,
Flag::NonContacts,
Flag::Groups,
Flag::Channels,
Flag::Bots,
Flag::NoMuted,
Flag::NoArchived,
Flag::NoRead
};
class FilterChatsPreview final : public Ui::RpWidget {
public:
FilterChatsPreview(
not_null<QWidget*> parent,
Flags flags,
const base::flat_set<not_null<History*>> &peers);
[[nodiscard]] rpl::producer<Flag> flagRemoved() const;
[[nodiscard]] rpl::producer<not_null<History*>> peerRemoved() const;
int resizeGetHeight(int newWidth) override;
private:
using Button = base::unique_qptr<Ui::IconButton>;
struct FlagButton {
Flag flag = Flag();
Button button;
};
struct PeerButton {
not_null<History*> history;
Button button;
};
void paintEvent(QPaintEvent *e) override;
void setup(
Flags flags,
const base::flat_set<not_null<History*>> &peers);
void refresh();
void removeFlag(Flag flag);
void removePeer(not_null<History*> history);
void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const;
std::vector<FlagButton> _removeFlag;
std::vector<PeerButton> _removePeer;
rpl::event_stream<Flag> _flagRemoved;
rpl::event_stream<not_null<History*>> _peerRemoved;
};
class FilterRowButton final : public Ui::RippleButton {
public:
@ -196,173 +141,6 @@ private:
: tr::lng_filters_no_chats(tr::now);
}
FilterChatsPreview::FilterChatsPreview(
not_null<QWidget*> parent,
Flags flags,
const base::flat_set<not_null<History*>> &peers)
: RpWidget(parent) {
setup(flags, peers);
}
void FilterChatsPreview::setup(
Flags flags,
const base::flat_set<not_null<History*>> &peers) {
const auto makeButton = [&](Fn<void()> handler) {
auto result = base::make_unique_q<Ui::IconButton>(
this,
st::windowFilterSmallRemove);
result->setClickedCallback(std::move(handler));
return result;
};
for (const auto flag : kAllTypes) {
if (flags & flag) {
_removeFlag.push_back({
flag,
makeButton([=] { removeFlag(flag); }) });
}
}
for (const auto history : peers) {
_removePeer.push_back({
history,
makeButton([=] { removePeer(history); }) });
}
refresh();
}
void FilterChatsPreview::refresh() {
resizeToWidth(width());
}
int FilterChatsPreview::resizeGetHeight(int newWidth) {
const auto right = st::windowFilterSmallRemoveRight;
const auto add = (st::windowFilterSmallItem.height
- st::windowFilterSmallRemove.height) / 2;
auto top = 0;
const auto moveNextButton = [&](not_null<Ui::IconButton*> button) {
button->moveToRight(right, top + add, newWidth);
top += st::windowFilterSmallItem.height;
};
for (const auto &[flag, button] : _removeFlag) {
moveNextButton(button.get());
}
for (const auto &[history, button] : _removePeer) {
moveNextButton(button.get());
}
return top;
}
[[nodiscard]] QString TypeName(Flag flag) {
switch (flag) {
case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now);
case Flag::NonContacts:
return tr::lng_filters_type_non_contacts(tr::now);
case Flag::Groups: return tr::lng_filters_type_groups(tr::now);
case Flag::Channels: return tr::lng_filters_type_channels(tr::now);
case Flag::Bots: return tr::lng_filters_type_bots(tr::now);
case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now);
case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now);
case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now);
}
Unexpected("Flag in TypeName.");
}
void FilterChatsPreview::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
auto top = 0;
const auto &st = st::windowFilterSmallItem;
const auto iconLeft = st.photoPosition.x();
const auto iconTop = st.photoPosition.y();
const auto nameLeft = st.namePosition.x();
p.setFont(st::windowFilterSmallItem.nameStyle.font);
const auto nameTop = st.namePosition.y();
for (const auto &[flag, button] : _removeFlag) {
paintFlagIcon(p, iconLeft, top + iconTop, flag);
p.setPen(st::contactsNameFg);
p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag));
top += st.height;
}
for (const auto &[history, button] : _removePeer) {
history->peer->paintUserpicLeft(
p,
iconLeft,
top + iconTop,
width(),
st.photoSize);
history->peer->nameText().drawLeftElided(
p,
nameLeft,
top + nameTop,
button->x() - nameLeft,
width());
top += st.height;
}
}
void FilterChatsPreview::paintFlagIcon(
QPainter &p,
int left,
int top,
Flag flag) const {
const auto &color = [&]() -> const style::color& {
switch (flag) {
case Flag::Contacts: return st::historyPeer4UserpicBg;
case Flag::NonContacts: return st::historyPeer7UserpicBg;
case Flag::Groups: return st::historyPeer2UserpicBg;
case Flag::Channels: return st::historyPeer1UserpicBg;
case Flag::Bots: return st::historyPeer6UserpicBg;
case Flag::NoMuted: return st::historyPeer6UserpicBg;
case Flag::NoArchived: return st::historyPeer4UserpicBg;
case Flag::NoRead: return st::historyPeer7UserpicBg;
}
Unexpected("Flag in color paintFlagIcon.");
}();
const auto &icon = [&]() -> const style::icon& {
switch (flag) {
case Flag::Contacts: return st::windowFilterTypeContacts;
case Flag::NonContacts: return st::windowFilterTypeNonContacts;
case Flag::Groups: return st::windowFilterTypeGroups;
case Flag::Channels: return st::windowFilterTypeChannels;
case Flag::Bots: return st::windowFilterTypeBots;
case Flag::NoMuted: return st::windowFilterTypeNoMuted;
case Flag::NoArchived: return st::windowFilterTypeNoArchived;
case Flag::NoRead: return st::windowFilterTypeNoRead;
}
Unexpected("Flag in icon paintFlagIcon.");
}();
const auto size = st::windowFilterSmallItem.photoSize;
const auto rect = QRect(left, top, size, size);
auto hq = PainterHighQualityEnabler(p);
p.setBrush(color->b);
p.setPen(Qt::NoPen);
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}
void FilterChatsPreview::removeFlag(Flag flag) {
const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag);
Assert(i != end(_removeFlag));
_removeFlag.erase(i);
refresh();
_flagRemoved.fire_copy(flag);
}
void FilterChatsPreview::removePeer(not_null<History*> history) {
const auto i = ranges::find(_removePeer, history, &PeerButton::history);
Assert(i != end(_removePeer));
_removePeer.erase(i);
refresh();
_peerRemoved.fire_copy(history);
}
rpl::producer<Flag> FilterChatsPreview::flagRemoved() const {
return _flagRemoved.events();
}
rpl::producer<not_null<History*>> FilterChatsPreview::peerRemoved() const {
return _peerRemoved.events();
}
FilterRowButton::FilterRowButton(
not_null<QWidget*> parent,
not_null<Main::Session*> session,
@ -609,7 +387,7 @@ void ManageFiltersPrepare::SetupBox(
button->updateData(result);
};
window->window().show(Box(
EditBox,
EditFilterBox,
window,
found->filter,
crl::guard(button, doneCallback)));
@ -633,7 +411,7 @@ void ManageFiltersPrepare::SetupBox(
addFilter(result);
};
window->window().show(Box(
EditBox,
EditFilterBox,
window,
Data::ChatFilter(),
crl::guard(box, doneCallback)));
@ -765,145 +543,3 @@ void ManageFiltersPrepare::SetupBox(
box->boxClosing() | rpl::start_with_next(save, box->lifetime());
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
}
void SetupChatsPreview(
not_null<Ui::VerticalLayout*> content,
not_null<Data::ChatFilter*> data,
Flags flags,
ExceptionPeersGetter peers) {
const auto preview = content->add(object_ptr<FilterChatsPreview>(
content,
data->flags() & flags,
(data->*peers)()));
preview->flagRemoved(
) | rpl::start_with_next([=](Flag flag) {
*data = Data::ChatFilter(
data->id(),
data->title(),
(data->flags() & ~flag),
data->always(),
data->never());
}, preview->lifetime());
preview->peerRemoved(
) | rpl::start_with_next([=](not_null<History*> history) {
auto always = data->always();
auto never = data->never();
always.remove(history);
never.remove(history);
*data = Data::ChatFilter(
data->id(),
data->title(),
data->flags(),
std::move(always),
std::move(never));
}, preview->lifetime());
}
void ManageFiltersPrepare::EditBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window,
const Data::ChatFilter &filter,
Fn<void(const Data::ChatFilter &)> doneCallback) {
const auto creating = filter.title().isEmpty();
box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
const auto content = box->verticalLayout();
const auto name = content->add(
object_ptr<Ui::InputField>(
box,
st::defaultInputField,
tr::lng_filters_new_name(),
filter.title()),
st::markdownLinkFieldPadding);
name->setMaxLength(kMaxFilterTitleLength);
const auto data = box->lifetime().make_state<Data::ChatFilter>(filter);
constexpr auto kTypes = Flag::Contacts
| Flag::NonContacts
| Flag::Groups
| Flag::Channels
| Flag::Bots;
constexpr auto kExcludeTypes = Flag::NoMuted
| Flag::NoArchived
| Flag::NoRead;
box->setFocusCallback([=] {
name->setFocusFast();
});
AddSkip(content);
AddDivider(content);
AddSkip(content);
AddSubsectionTitle(content, tr::lng_filters_include());
SetupChatsPreview(
content,
data,
kTypes,
&Data::ChatFilter::always);
AddButton(
content,
tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
st::settingsUpdate
)->setClickedCallback([=] {
});
AddSkip(content);
AddDividerText(content, tr::lng_filters_include_about());
AddSkip(content);
AddSubsectionTitle(content, tr::lng_filters_exclude());
SetupChatsPreview(
content,
data,
kExcludeTypes,
&Data::ChatFilter::never);
AddButton(
content,
tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
st::settingsUpdate
)->setClickedCallback([=] {
});
AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_filters_exclude_about(),
st::boxDividerLabel),
st::settingsDividerLabelPadding);
const auto save = [=] {
const auto title = name->getLastText().trimmed();
if (title.isEmpty()) {
name->showError();
return;
} else if (!(data->flags() & kTypes) && data->always().empty()) {
window->window().showToast(tr::lng_filters_empty(tr::now));
return;
} else if ((data->flags() == (kTypes | Flag::NoArchived))
&& data->always().empty()
&& data->never().empty()) {
window->window().showToast(tr::lng_filters_default(tr::now));
return;
}
const auto result = Data::ChatFilter(
data->id(),
title,
data->flags(),
data->always(),
data->never());
box->closeBox();
doneCallback(result);
};
box->addButton(
creating ? tr::lng_filters_create_button() : tr::lng_settings_save(),
save);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}

View File

@ -38,11 +38,6 @@ private:
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window,
const std::vector<Suggested> &suggested);
static void EditBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window,
const Data::ChatFilter &filter,
Fn<void(const Data::ChatFilter &)> doneCallback);
const not_null<Window::SessionController*> _window;
const not_null<ApiWrap*> _api;

View File

@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_chat_filters.h"
#include "boxes/manage_filters_box.h"
#include "boxes/filters/manage_filters_box.h"
#include "lang/lang_keys.h"
#include "styles/style_widgets.h"
#include "styles/style_window.h"