mirror of https://github.com/procxx/kepka.git
Allow adding chats to filter exceptions.
This commit is contained in:
parent
d5bd9fa54d
commit
13fe0b6272
|
@ -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
|
||||
|
|
|
@ -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(); });
|
||||
}
|
|
@ -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);
|
|
@ -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));
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
};
|
|
@ -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(); });
|
||||
}
|
|
@ -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;
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue