View filter box, removing chats and chat types.

This commit is contained in:
John Preston 2020-03-13 10:56:03 +04:00
parent a091e73686
commit d5bd9fa54d
34 changed files with 540 additions and 65 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -2240,14 +2240,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_recommended" = "Recommended"; "lng_filters_recommended" = "Recommended";
"lng_filters_recommended_add" = "Add"; "lng_filters_recommended_add" = "Add";
"lng_filters_restore" = "Undo"; "lng_filters_restore" = "Undo";
"lng_filters_new" = "New folder"; "lng_filters_new" = "New Folder";
"lng_filters_edit" = "Edit Folder";
"lng_filters_new_name" = "Folder name"; "lng_filters_new_name" = "Folder name";
"lng_filters_add_chats" = "Add chats"; "lng_filters_add_chats" = "Add chats";
"lng_filters_include" = "Include"; "lng_filters_include" = "Included Chats";
"lng_filters_include_about" = "Choose chats and types of chats that will appear in this folder."; "lng_filters_include_about" = "Choose chats and types of chats that will appear in this folder.";
"lng_filters_exclude" = "Exclude"; "lng_filters_exclude" = "Excluded Chats";
"lng_filters_exclude_about" = "Choose chats and types of chats that will never appear in this folder."; "lng_filters_exclude_about" = "Choose chats and types of chats that will never appear in this folder.";
"lng_filters_create_button" = "Create";
"lng_filters_add_title" = "Add Chats"; "lng_filters_add_title" = "Add Chats";
"lng_filters_include_title" = "Include Chats";
"lng_filters_exclude_title" = "Exclude Chats";
"lng_filters_edit_types" = "Chat types"; "lng_filters_edit_types" = "Chat types";
"lng_filters_edit_chats" = "Chats"; "lng_filters_edit_chats" = "Chats";
"lng_filters_include_contacts" = "Contacts"; "lng_filters_include_contacts" = "Contacts";
@ -2260,6 +2264,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_exclude_archived" = "Archived"; "lng_filters_exclude_archived" = "Archived";
"lng_filters_add" = "Done"; "lng_filters_add" = "Done";
"lng_filters_limit" = "Sorry, you have reached folders limit."; "lng_filters_limit" = "Sorry, you have reached folders limit.";
"lng_filters_empty" = "Please choose at least one chat for this folder.";
"lng_filters_default" = "Please change at least one rule for this folder.";
"lng_filters_type_contacts" = "Contacts";
"lng_filters_type_non_contacts" = "Non-Contacts";
"lng_filters_type_groups" = "Groups";
"lng_filters_type_channels" = "Channels";
"lng_filters_type_bots" = "Bots";
"lng_filters_type_no_archived" = "No Archived";
"lng_filters_type_no_muted" = "No Muted";
"lng_filters_type_no_read" = "No Read";
// Wnd specific // Wnd specific

View File

@ -10,14 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_peer.h"
#include "history/history.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -25,11 +29,72 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_window.h"
namespace { namespace {
constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000);
constexpr auto kFiltersLimit = 10; 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 { class FilterRowButton final : public Ui::RippleButton {
public: public:
@ -43,6 +108,7 @@ public:
const QString &description); const QString &description);
void setRemoved(bool removed); void setRemoved(bool removed);
void updateData(const Data::ChatFilter &filter);
[[nodiscard]] rpl::producer<> removeRequests() const; [[nodiscard]] rpl::producer<> removeRequests() const;
[[nodiscard]] rpl::producer<> restoreRequests() const; [[nodiscard]] rpl::producer<> restoreRequests() const;
@ -57,6 +123,7 @@ private:
FilterRowButton( FilterRowButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
Main::Session *session,
const Data::ChatFilter &filter, const Data::ChatFilter &filter,
const QString &description, const QString &description,
State state); State state);
@ -67,6 +134,8 @@ private:
void setState(State state, bool force = false); void setState(State state, bool force = false);
void updateButtonsVisibility(); void updateButtonsVisibility();
Main::Session *_session = nullptr;
Ui::IconButton _remove; Ui::IconButton _remove;
Ui::RoundButton _restore; Ui::RoundButton _restore;
Ui::RoundButton _add; Ui::RoundButton _add;
@ -101,10 +170,16 @@ private:
[[nodiscard]] int ComputeCount( [[nodiscard]] int ComputeCount(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const Data::ChatFilter &filter) { const Data::ChatFilter &filter,
bool check = false) {
const auto &list = session->data().chatsFilters().list(); const auto &list = session->data().chatsFilters().list();
const auto id = filter.id(); const auto id = filter.id();
if (ranges::contains(list, id, &Data::ChatFilter::id)) { const auto i = ranges::find(list, id, &Data::ChatFilter::id);
if (i != end(list)
&& (!check
|| (i->flags() == filter.flags()
&& i->always() == filter.always()
&& i->never() == filter.never()))) {
const auto chats = session->data().chatsFilters().chatsList(id); const auto chats = session->data().chatsFilters().chatsList(id);
return chats->indexed()->size(); return chats->indexed()->size();
} }
@ -113,19 +188,188 @@ private:
[[nodiscard]] QString ComputeCountString( [[nodiscard]] QString ComputeCountString(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const Data::ChatFilter &filter) { const Data::ChatFilter &filter,
const auto count = ComputeCount(session, filter); bool check = false) {
const auto count = ComputeCount(session, filter, check);
return count return count
? tr::lng_filters_chats_count(tr::now, lt_count_short, count) ? tr::lng_filters_chats_count(tr::now, lt_count_short, count)
: tr::lng_filters_no_chats(tr::now); : 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( FilterRowButton::FilterRowButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Main::Session*> session, not_null<Main::Session*> session,
const Data::ChatFilter &filter) const Data::ChatFilter &filter)
: FilterRowButton( : FilterRowButton(
parent, parent,
session,
filter, filter,
ComputeCountString(session, filter), ComputeCountString(session, filter),
State::Normal) { State::Normal) {
@ -135,15 +379,17 @@ FilterRowButton::FilterRowButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
const Data::ChatFilter &filter, const Data::ChatFilter &filter,
const QString &description) const QString &description)
: FilterRowButton(parent, filter, description, State::Suggested) { : FilterRowButton(parent, nullptr, filter, description, State::Suggested) {
} }
FilterRowButton::FilterRowButton( FilterRowButton::FilterRowButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
Main::Session *session,
const Data::ChatFilter &filter, const Data::ChatFilter &filter,
const QString &status, const QString &status,
State state) State state)
: RippleButton(parent, st::defaultRippleAnimation) : RippleButton(parent, st::defaultRippleAnimation)
, _session(session)
, _remove(this, st::filtersRemove) , _remove(this, st::filtersRemove)
, _restore(this, tr::lng_filters_restore(), st::stickersUndoRemove) , _restore(this, tr::lng_filters_restore(), st::stickersUndoRemove)
, _add(this, tr::lng_filters_recommended_add(), st::stickersTrendingAdd) , _add(this, tr::lng_filters_recommended_add(), st::stickersTrendingAdd)
@ -155,6 +401,14 @@ void FilterRowButton::setRemoved(bool removed) {
setState(removed ? State::Removed : State::Normal); setState(removed ? State::Removed : State::Normal);
} }
void FilterRowButton::updateData(const Data::ChatFilter &filter) {
Expects(_session != nullptr);
_title.setText(st::contactsNameStyle, filter.title());
_status = ComputeCountString(_session, filter, true);
update();
}
void FilterRowButton::setState(State state, bool force) { void FilterRowButton::setState(State state, bool force) {
if (!force && _state == state) { if (!force && _state == state) {
return; return;
@ -292,13 +546,15 @@ void ManageFiltersPrepare::showBox() {
} }
void ManageFiltersPrepare::showBoxWithSuggested() { void ManageFiltersPrepare::showBoxWithSuggested() {
_window->window().show(Box(CreateBox, _window, _suggested)); _window->window().show(Box(SetupBox, _window, _suggested));
} }
void ManageFiltersPrepare::CreateBox( void ManageFiltersPrepare::SetupBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
const std::vector<Suggested> &suggestions) { const std::vector<Suggested> &suggestions) {
box->setTitle(tr::lng_filters_title());
struct FilterRow { struct FilterRow {
not_null<FilterRowButton*> button; not_null<FilterRowButton*> button;
Data::ChatFilter filter; Data::ChatFilter filter;
@ -306,11 +562,9 @@ void ManageFiltersPrepare::CreateBox(
bool added = false; bool added = false;
}; };
box->setTitle(tr::lng_filters_title());
const auto session = &window->session(); const auto session = &window->session();
const auto content = box->verticalLayout(); const auto content = box->verticalLayout();
Settings::AddSubsectionTitle(content, tr::lng_filters_subtitle()); AddSubsectionTitle(content, tr::lng_filters_subtitle());
const auto rows = box->lifetime().make_state<std::vector<FilterRow>>(); const auto rows = box->lifetime().make_state<std::vector<FilterRow>>();
const auto find = [=](not_null<FilterRowButton*> button) { const auto find = [=](not_null<FilterRowButton*> button) {
@ -319,8 +573,14 @@ void ManageFiltersPrepare::CreateBox(
return &*i; return &*i;
}; };
const auto countNonRemoved = [=] { const auto countNonRemoved = [=] {
};
const auto showLimitReached = [=] {
const auto removed = ranges::count_if(*rows, &FilterRow::removed); const auto removed = ranges::count_if(*rows, &FilterRow::removed);
return rows->size() - removed; if (rows->size() < kFiltersLimit + removed) {
return false;
}
window->window().showToast(tr::lng_filters_limit(tr::now));
return true;
}; };
const auto wrap = content->add(object_ptr<Ui::VerticalLayout>(content)); const auto wrap = content->add(object_ptr<Ui::VerticalLayout>(content));
const auto addFilter = [=](const Data::ChatFilter &filter) { const auto addFilter = [=](const Data::ChatFilter &filter) {
@ -333,11 +593,27 @@ void ManageFiltersPrepare::CreateBox(
}, button->lifetime()); }, button->lifetime());
button->restoreRequests( button->restoreRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (countNonRemoved() < kFiltersLimit) { if (showLimitReached()) {
button->setRemoved(false); return;
find(button)->removed = false;
} }
button->setRemoved(false);
find(button)->removed = false;
}, button->lifetime()); }, button->lifetime());
button->setClickedCallback([=] {
const auto found = find(button);
if (found->removed) {
return;
}
const auto doneCallback = [=](const Data::ChatFilter &result) {
find(button)->filter = result;
button->updateData(result);
};
window->window().show(Box(
EditBox,
window,
found->filter,
crl::guard(button, doneCallback)));
});
rows->push_back({ button, filter }); rows->push_back({ button, filter });
}; };
const auto &list = session->data().chatsFilters().list(); const auto &list = session->data().chatsFilters().list();
@ -345,11 +621,24 @@ void ManageFiltersPrepare::CreateBox(
addFilter(filter); addFilter(filter);
} }
Settings::AddButton( AddButton(
content, content,
tr::lng_filters_create() | Ui::Text::ToUpper(), tr::lng_filters_create() | Ui::Text::ToUpper(),
st::settingsUpdate); st::settingsUpdate
Settings::AddSkip(content); )->setClickedCallback([=] {
if (showLimitReached()) {
return;
}
const auto doneCallback = [=](const Data::ChatFilter &result) {
addFilter(result);
};
window->window().show(Box(
EditBox,
window,
Data::ChatFilter(),
crl::guard(box, doneCallback)));
});
AddSkip(content);
const auto emptyAbout = content->add( const auto emptyAbout = content->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>( object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
content, content,
@ -365,24 +654,15 @@ void ManageFiltersPrepare::CreateBox(
object_ptr<Ui::VerticalLayout>(content)) object_ptr<Ui::VerticalLayout>(content))
)->setDuration(0); )->setDuration(0);
const auto aboutRows = nonEmptyAbout->entity(); const auto aboutRows = nonEmptyAbout->entity();
Settings::AddDividerText(aboutRows, tr::lng_filters_about()); AddDividerText(aboutRows, tr::lng_filters_about());
Settings::AddSkip(aboutRows); AddSkip(aboutRows);
Settings::AddSubsectionTitle(aboutRows, tr::lng_filters_recommended()); AddSubsectionTitle(aboutRows, tr::lng_filters_recommended());
const auto changed = box->lifetime().make_state<bool>();
const auto suggested = box->lifetime().make_state<rpl::variable<int>>(); const auto suggested = box->lifetime().make_state<rpl::variable<int>>();
for (const auto &suggestion : suggestions) { for (const auto &suggestion : suggestions) {
const auto filter = suggestion.filter; const auto &filter = suggestion.filter;
const auto already = [&] { if (ranges::contains(list, filter)) {
for (const auto &entry : list) {
if (entry.flags() == filter.flags()
&& entry.always() == filter.always()
&& entry.never() == filter.never()) {
return true;
}
}
return false;
}();
if (already) {
continue; continue;
} }
*suggested = suggested->current() + 1; *suggested = suggested->current() + 1;
@ -392,6 +672,9 @@ void ManageFiltersPrepare::CreateBox(
suggestion.description)); suggestion.description));
button->addRequests( button->addRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (showLimitReached()) {
return;
}
addFilter(filter); addFilter(filter);
*suggested = suggested->current() - 1; *suggested = suggested->current() - 1;
delete button; delete button;
@ -427,15 +710,19 @@ void ManageFiltersPrepare::CreateBox(
const auto save = [=] { const auto save = [=] {
auto ids = prepareGoodIdsForNewFilters(); auto ids = prepareGoodIdsForNewFilters();
auto requests = std::deque<MTPmessages_UpdateDialogFilter>(); using Requests = std::vector<MTPmessages_UpdateDialogFilter>;
auto addRequests = Requests(), removeRequests = Requests();
auto &realFilters = session->data().chatsFilters(); auto &realFilters = session->data().chatsFilters();
const auto &list = realFilters.list(); const auto &list = realFilters.list();
auto order = QVector<MTPint>(); auto order = QVector<MTPint>();
for (const auto &row : *rows) { for (const auto &row : *rows) {
const auto id = row.filter.id(); const auto id = row.filter.id();
const auto removed = row.removed; const auto removed = row.removed;
if (removed const auto i = ranges::find(list, id, &Data::ChatFilter::id);
&& !ranges::contains(list, id, &Data::ChatFilter::id)) { if (removed && i == end(list)) {
continue;
} else if (!removed && i != end(list) && *i == row.filter) {
order.push_back(MTP_int(id));
continue; continue;
} }
const auto newId = ids.take(id).value_or(id); const auto newId = ids.take(id).value_or(id);
@ -447,9 +734,9 @@ void ManageFiltersPrepare::CreateBox(
MTP_int(newId), MTP_int(newId),
tl); tl);
if (removed) { if (removed) {
requests.push_front(request); removeRequests.push_back(request);
} else { } else {
requests.push_back(request); addRequests.push_back(request);
order.push_back(MTP_int(newId)); order.push_back(MTP_int(newId));
} }
realFilters.apply(MTP_updateDialogFilter( realFilters.apply(MTP_updateDialogFilter(
@ -460,12 +747,13 @@ void ManageFiltersPrepare::CreateBox(
tl)); tl));
} }
auto previousId = mtpRequestId(0); auto previousId = mtpRequestId(0);
auto &&requests = ranges::view::concat(removeRequests, addRequests);
for (auto &request : requests) { for (auto &request : requests) {
previousId = session->api().request( previousId = session->api().request(
std::move(request) std::move(request)
).afterRequest(previousId).send(); ).afterRequest(previousId).send();
} }
if (!order.isEmpty()) { if (!order.isEmpty() && !addRequests.empty()) {
realFilters.apply( realFilters.apply(
MTP_updateDialogFilterOrder(MTP_vector(order))); MTP_updateDialogFilterOrder(MTP_vector(order)));
session->api().request(MTPmessages_UpdateDialogFiltersOrder( session->api().request(MTPmessages_UpdateDialogFiltersOrder(
@ -474,6 +762,148 @@ void ManageFiltersPrepare::CreateBox(
} }
box->closeBox(); box->closeBox();
}; };
box->addButton(tr::lng_settings_save(), save); box->boxClosing() | rpl::start_with_next(save, box->lifetime());
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); 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

@ -34,10 +34,15 @@ private:
}; };
void showBoxWithSuggested(); void showBoxWithSuggested();
static void CreateBox( static void SetupBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
const std::vector<Suggested> &suggested); 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<Window::SessionController*> _window;
const not_null<ApiWrap*> _api; const not_null<ApiWrap*> _api;

View File

@ -40,11 +40,11 @@ ChatFilter ChatFilter::FromTL(
const auto flags = (data.is_contacts() ? Flag::Contacts : Flag(0)) const auto flags = (data.is_contacts() ? Flag::Contacts : Flag(0))
| (data.is_non_contacts() ? Flag::NonContacts : Flag(0)) | (data.is_non_contacts() ? Flag::NonContacts : Flag(0))
| (data.is_groups() ? Flag::Groups : Flag(0)) | (data.is_groups() ? Flag::Groups : Flag(0))
| (data.is_broadcasts() ? Flag::Broadcasts : Flag(0)) | (data.is_broadcasts() ? Flag::Channels : Flag(0))
| (data.is_bots() ? Flag::Bots : Flag(0)) | (data.is_bots() ? Flag::Bots : Flag(0))
| (data.is_exclude_muted() ? Flag::NoMuted : Flag(0)) | (data.is_exclude_muted() ? Flag::NoMuted : Flag(0))
| (data.is_exclude_read() ? Flag::NoRead : Flag(0)) | (data.is_exclude_read() ? Flag::NoRead : Flag(0))
| (data.is_exclude_archived() ? Flag::NoArchive : Flag(0)); | (data.is_exclude_archived() ? Flag::NoArchived : Flag(0));
auto &&to_histories = ranges::view::transform([&]( auto &&to_histories = ranges::view::transform([&](
const MTPInputPeer &data) { const MTPInputPeer &data) {
const auto peer = data.match([&](const MTPDinputPeerUser &data) { const auto peer = data.match([&](const MTPDinputPeerUser &data) {
@ -87,11 +87,11 @@ MTPDialogFilter ChatFilter::tl() const {
| ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0))
| ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0)) | ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0))
| ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0)) | ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0))
| ((_flags & Flag::Broadcasts) ? TLFlag::f_broadcasts : TLFlag(0)) | ((_flags & Flag::Channels) ? TLFlag::f_broadcasts : TLFlag(0))
| ((_flags & Flag::Bots) ? TLFlag::f_bots : TLFlag(0)) | ((_flags & Flag::Bots) ? TLFlag::f_bots : TLFlag(0))
| ((_flags & Flag::NoMuted) ? TLFlag::f_exclude_muted : TLFlag(0)) | ((_flags & Flag::NoMuted) ? TLFlag::f_exclude_muted : TLFlag(0))
| ((_flags & Flag::NoRead) ? TLFlag::f_exclude_read : TLFlag(0)) | ((_flags & Flag::NoRead) ? TLFlag::f_exclude_read : TLFlag(0))
| ((_flags & Flag::NoArchive) | ((_flags & Flag::NoArchived)
? TLFlag::f_exclude_archived ? TLFlag::f_exclude_archived
: TLFlag(0)); : TLFlag(0));
auto always = QVector<MTPInputPeer>(); auto always = QVector<MTPInputPeer>();
@ -147,7 +147,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
return Flag::Groups; return Flag::Groups;
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannel()) {
if (channel->isBroadcast()) { if (channel->isBroadcast()) {
return Flag::Broadcasts; return Flag::Channels;
} else { } else {
return Flag::Groups; return Flag::Groups;
} }
@ -162,7 +162,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
|| ((_flags & flag) || ((_flags & flag)
&& (!(_flags & Flag::NoMuted) || !history->mute()) && (!(_flags & Flag::NoMuted) || !history->mute())
&& (!(_flags & Flag::NoRead) || history->unreadCountForBadge()) && (!(_flags & Flag::NoRead) || history->unreadCountForBadge())
&& (!(_flags & Flag::NoArchive) && (!(_flags & Flag::NoArchived)
|| (history->folderKnown() && !history->folder()))) || (history->folderKnown() && !history->folder())))
|| _always.contains(history); || _always.contains(history);
} }
@ -172,9 +172,9 @@ ChatFilters::ChatFilters(not_null<Session*> owner) : _owner(owner) {
//const auto all = Flag::Contacts //const auto all = Flag::Contacts
// | Flag::NonContacts // | Flag::NonContacts
// | Flag::Groups // | Flag::Groups
// | Flag::Broadcasts // | Flag::Channels
// | Flag::Bots // | Flag::Bots
// | Flag::NoArchive; // | Flag::NoArchived;
//_list.push_back( //_list.push_back(
// ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {})); // ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {}));
//_list.push_back( //_list.push_back(

View File

@ -25,11 +25,11 @@ public:
Contacts = 0x01, Contacts = 0x01,
NonContacts = 0x02, NonContacts = 0x02,
Groups = 0x04, Groups = 0x04,
Broadcasts = 0x08, Channels = 0x08,
Bots = 0x10, Bots = 0x10,
NoMuted = 0x20, NoMuted = 0x20,
NoRead = 0x40, NoRead = 0x40,
NoArchive = 0x80, NoArchived = 0x80,
}; };
friend constexpr inline bool is_flag_type(Flag) { return true; }; friend constexpr inline bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
@ -64,6 +64,17 @@ private:
}; };
inline bool operator==(const ChatFilter &a, const ChatFilter &b) {
return (a.title() == b.title())
&& (a.flags() == b.flags())
&& (a.always() == b.always())
&& (a.never() == b.never());
}
inline bool operator!=(const ChatFilter &a, const ChatFilter &b) {
return !(a == b);
}
class ChatFilters final { class ChatFilters final {
public: public:
explicit ChatFilters(not_null<Session*> owner); explicit ChatFilters(not_null<Session*> owner);

View File

@ -1173,14 +1173,6 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
if (!_checkStreamStatus(stream)) return false; if (!_checkStreamStatus(stream)) return false;
Global::SetDialogsFiltersEnabled(enabled == 1); Global::SetDialogsFiltersEnabled(enabled == 1);
auto mode = FilterId(0);
if (enabled) {
mode = FilterId(modeInt);
if (mode == 1) { // #TODO filters
}
}
Global::SetDialogsFilterId(mode);
} break; } break;
case dbiModerateMode: { case dbiModerateMode: {

View File

@ -299,6 +299,23 @@ windowFiltersCustom: SideBarButton(windowFiltersButton) {
windowFiltersSetup: SideBarButton(windowFiltersButton) { windowFiltersSetup: SideBarButton(windowFiltersButton) {
icon: icon {{ "filters_setup", sideBarIconFg }}; icon: icon {{ "filters_setup", sideBarIconFg }};
} }
windowFilterSmallItem: PeerListItem(defaultPeerListItem) {
height: 44px;
photoPosition: point(15px, 5px);
namePosition: point(62px, 14px);
photoSize: 34px;
}
windowFilterSmallRemove: IconButton(notifyClose) {
}
windowFilterSmallRemoveRight: 10px;
windowFilterTypeContacts: icon {{ "filters_type_contacts", historyPeerUserpicFg }};
windowFilterTypeNonContacts: icon {{ "filters_type_noncontacts", historyPeerUserpicFg }};
windowFilterTypeGroups: icon {{ "filters_type_groups", historyPeerUserpicFg }};
windowFilterTypeChannels: icon {{ "filters_type_channels", historyPeerUserpicFg }};
windowFilterTypeBots: icon {{ "filters_type_bots", historyPeerUserpicFg }};
windowFilterTypeNoMuted: icon {{ "filters_type_muted", historyPeerUserpicFg }};
windowFilterTypeNoArchived: icon {{ "filters_type_archived", historyPeerUserpicFg }};
windowFilterTypeNoRead: icon {{ "filters_type_read", historyPeerUserpicFg }};
// Mac specific // Mac specific

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_account.h" #include "main/main_account.h"
#include "ui/layers/box_content.h" #include "ui/layers/box_content.h"
#include "ui/layers/layer_widget.h" #include "ui/layers/layer_widget.h"
#include "ui/toast/toast.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/themes/window_theme_editor.h" #include "window/themes/window_theme_editor.h"
@ -80,6 +81,10 @@ void Controller::showSettings() {
_widget.showSettings(); _widget.showSettings();
} }
void Controller::showToast(const QString &text) {
Ui::Toast::Show(_widget.bodyWidget(), text);
}
void Controller::showBox( void Controller::showBox(
object_ptr<Ui::BoxContent> content, object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options, Ui::LayerOptions options,

View File

@ -52,6 +52,7 @@ public:
showBox(std::move(content), options, animated); showBox(std::move(content), options, animated);
return result; return result;
} }
void showToast(const QString &text);
void showRightColumn(object_ptr<TWidget> widget); void showRightColumn(object_ptr<TWidget> widget);
void sideBarChanged(); void sideBarChanged();

View File

@ -37,11 +37,11 @@ enum class Type {
const auto all = Flag::Contacts const auto all = Flag::Contacts
| Flag::NonContacts | Flag::NonContacts
| Flag::Groups | Flag::Groups
| Flag::Broadcasts | Flag::Channels
| Flag::Bots; | Flag::Bots;
const auto removed = Flag::NoRead | Flag::NoMuted; const auto removed = Flag::NoRead | Flag::NoMuted;
const auto people = Flag::Contacts | Flag::NonContacts; const auto people = Flag::Contacts | Flag::NonContacts;
const auto allNoArchive = all | Flag::NoArchive; const auto allNoArchive = all | Flag::NoArchived;
if (!filter.always().empty() if (!filter.always().empty()
|| !filter.never().empty() || !filter.never().empty()
|| !(filter.flags() & all)) { || !(filter.flags() & all)) {
@ -52,7 +52,7 @@ enum class Type {
return Type::People; return Type::People;
} else if ((filter.flags() & all) == Flag::Groups) { } else if ((filter.flags() & all) == Flag::Groups) {
return Type::Groups; return Type::Groups;
} else if ((filter.flags() & all) == Flag::Broadcasts) { } else if ((filter.flags() & all) == Flag::Channels) {
return Type::Channels; return Type::Channels;
} else if ((filter.flags() & all) == Flag::Bots) { } else if ((filter.flags() & all) == Flag::Bots) {
return Type::Bots; return Type::Bots;