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_add" = "Add";
"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_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_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_create_button" = "Create";
"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_chats" = "Chats";
"lng_filters_include_contacts" = "Contacts";
@ -2260,6 +2264,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_exclude_archived" = "Archived";
"lng_filters_add" = "Done";
"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

View File

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

@ -34,10 +34,15 @@ private:
};
void showBoxWithSuggested();
static void CreateBox(
static void SetupBox(
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

@ -40,11 +40,11 @@ ChatFilter ChatFilter::FromTL(
const auto flags = (data.is_contacts() ? Flag::Contacts : Flag(0))
| (data.is_non_contacts() ? Flag::NonContacts : 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_exclude_muted() ? Flag::NoMuted : 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([&](
const MTPInputPeer &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::NonContacts) ? TLFlag::f_non_contacts : 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::NoMuted) ? TLFlag::f_exclude_muted : TLFlag(0))
| ((_flags & Flag::NoRead) ? TLFlag::f_exclude_read : TLFlag(0))
| ((_flags & Flag::NoArchive)
| ((_flags & Flag::NoArchived)
? TLFlag::f_exclude_archived
: TLFlag(0));
auto always = QVector<MTPInputPeer>();
@ -147,7 +147,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
return Flag::Groups;
} else if (const auto channel = peer->asChannel()) {
if (channel->isBroadcast()) {
return Flag::Broadcasts;
return Flag::Channels;
} else {
return Flag::Groups;
}
@ -162,7 +162,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
|| ((_flags & flag)
&& (!(_flags & Flag::NoMuted) || !history->mute())
&& (!(_flags & Flag::NoRead) || history->unreadCountForBadge())
&& (!(_flags & Flag::NoArchive)
&& (!(_flags & Flag::NoArchived)
|| (history->folderKnown() && !history->folder())))
|| _always.contains(history);
}
@ -172,9 +172,9 @@ ChatFilters::ChatFilters(not_null<Session*> owner) : _owner(owner) {
//const auto all = Flag::Contacts
// | Flag::NonContacts
// | Flag::Groups
// | Flag::Broadcasts
// | Flag::Channels
// | Flag::Bots
// | Flag::NoArchive;
// | Flag::NoArchived;
//_list.push_back(
// ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {}));
//_list.push_back(

View File

@ -25,11 +25,11 @@ public:
Contacts = 0x01,
NonContacts = 0x02,
Groups = 0x04,
Broadcasts = 0x08,
Channels = 0x08,
Bots = 0x10,
NoMuted = 0x20,
NoRead = 0x40,
NoArchive = 0x80,
NoArchived = 0x80,
};
friend constexpr inline bool is_flag_type(Flag) { return true; };
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 {
public:
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;
Global::SetDialogsFiltersEnabled(enabled == 1);
auto mode = FilterId(0);
if (enabled) {
mode = FilterId(modeInt);
if (mode == 1) { // #TODO filters
}
}
Global::SetDialogsFilterId(mode);
} break;
case dbiModerateMode: {

View File

@ -299,6 +299,23 @@ windowFiltersCustom: SideBarButton(windowFiltersButton) {
windowFiltersSetup: SideBarButton(windowFiltersButton) {
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

View File

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

View File

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

View File

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