diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index d43041eb51..88836a89cb 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -1183,6 +1183,7 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; +dialogFilterDefault#363293ae = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1786,6 +1787,7 @@ payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; +payments.assignAppStoreTransaction#6299a12f transaction_id:string = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 868b95ecba..3fffdbef69 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -57,6 +57,8 @@ void ChangeFilterById( FilterId filterId, not_null history, bool add) { + Expects(filterId != 0); + const auto list = history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); if (i != end(list)) { @@ -99,7 +101,7 @@ ChooseFilterValidator::ChooseFilterValidator(not_null history) bool ChooseFilterValidator::canAdd() const { for (const auto &filter : _history->owner().chatsFilters().list()) { - if (!filter.contains(_history)) { + if (filter.id() && !filter.contains(_history)) { return true; } } @@ -107,6 +109,8 @@ bool ChooseFilterValidator::canAdd() const { } bool ChooseFilterValidator::canRemove(FilterId filterId) const { + Expects(filterId != 0); + const auto list = _history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); if (i != end(list)) { @@ -118,6 +122,8 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const { } bool ChooseFilterValidator::limitReached(FilterId filterId) const { + Expects(filterId != 0); + const auto list = _history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); const auto limit = _history->owner().pinnedChatsLimit(nullptr, filterId); @@ -142,6 +148,10 @@ void FillChooseFilterMenu( const auto validator = ChooseFilterValidator(history); for (const auto &filter : history->owner().chatsFilters().list()) { const auto id = filter.id(); + if (!id) { + continue; + } + const auto contains = filter.contains(history); const auto action = menu->addAction(filter.title(), [=] { if (filter.contains(history)) { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 127cb07bb1..4958398ace 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -668,6 +668,8 @@ void EditFilterBox( void EditExistingFilter( not_null window, FilterId id) { + Expects(id != 0); + const auto session = &window->session(); const auto &list = session->data().chatsFilters().list(); const auto i = ranges::find(list, id, &Data::ChatFilter::id); diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 3f67e4fda7..4ed74938d7 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -107,6 +107,8 @@ ChatFilter ChatFilter::FromTL( std::move(list), std::move(pinned), { never.begin(), never.end() }); + }, [](const MTPDdialogFilterDefault &d) { + return ChatFilter(); }); } @@ -220,6 +222,7 @@ bool ChatFilter::contains(not_null history) const { } ChatFilters::ChatFilters(not_null owner) : _owner(owner) { + _list.emplace_back(); crl::on_main(&owner->session(), [=] { load(); }); } @@ -295,6 +298,9 @@ void ChatFilters::received(const QVector &list) { applyRemove(position); changed = true; } + if (!ranges::contains(begin(_list), end(_list), 0, &ChatFilter::id)) { + _list.insert(begin(_list), ChatFilter()); + } if (changed || !_loaded) { _loaded = true; _listChanged.fire({}); @@ -352,6 +358,16 @@ void ChatFilters::remove(FilterId id) { _listChanged.fire({}); } +void ChatFilters::moveAllToFront() { + const auto i = ranges::find(_list, FilterId(), &ChatFilter::id); + if (!_list.empty() && i == begin(_list)) { + return; + } else if (i != end(_list)) { + _list.erase(i); + } + _list.insert(begin(_list), ChatFilter()); +} + void ChatFilters::applyRemove(int position) { Expects(position >= 0 && position < _list.size()); @@ -518,6 +534,10 @@ const std::vector &ChatFilters::list() const { return _list; } +bool ChatFilters::has() const { + return _list.size() > 1; +} + rpl::producer<> ChatFilters::changed() const { return _listChanged.events(); } diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 40bf34dda4..45b7d12511 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -99,8 +99,10 @@ public: void apply(const MTPUpdate &update); void set(ChatFilter filter); void remove(FilterId id); + void moveAllToFront(); [[nodiscard]] const std::vector &list() const; [[nodiscard]] rpl::producer<> changed() const; + [[nodiscard]] bool has() const; bool loadNextExceptions(bool chatsListLoaded); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 286dee0d9d..3416b9af3a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -268,7 +268,7 @@ Session::Session(not_null session) _chatsFilters->changed( ) | rpl::start_with_next([=] { - const auto enabled = !_chatsFilters->list().empty(); + const auto enabled = _chatsFilters->has(); if (enabled != session->settings().dialogsFiltersEnabled()) { session->settings().setDialogsFiltersEnabled(enabled); session->saveSettingsDelayed(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 860c853571..3f4af37037 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2706,7 +2706,7 @@ bool InnerWidget::chooseCollapsedRow() { } void InnerWidget::switchToFilter(FilterId filterId) { - const auto found = ranges::contains( + const auto found = filterId && ranges::contains( session().data().chatsFilters().list(), filterId, &Data::ChatFilter::id); @@ -3204,16 +3204,16 @@ void InnerWidget::setupShortcuts() { return false; }); - const auto filters = &session().data().chatsFilters().list(); - if (const auto filtersCount = int(filters->size())) { + if (session().data().chatsFilters().has()) { + const auto filters = &session().data().chatsFilters().list(); + const auto filtersCount = int(filters->size()); auto &&folders = ranges::views::zip( Shortcuts::kShowFolder, ranges::views::ints(0, ranges::unreachable)); - for (const auto [command, index] : folders) { const auto select = (command == Command::ShowFolderLast) ? filtersCount - : std::clamp(index, 0, filtersCount); + : std::clamp(index, 0, int(filtersCount)); request->check(command) && request->handle([=] { if (select <= filtersCount) { _controller->setActiveChatsFilter((select > 0) @@ -3256,15 +3256,16 @@ void InnerWidget::setupShortcuts() { const auto nearFolder = [=](bool isNext) { const auto id = _controller->activeChatsFilterCurrent(); const auto list = &session().data().chatsFilters().list(); - const auto index = (id != 0) - ? int(ranges::find(*list, id, &Data::ChatFilter::id) - - begin(*list)) - : -1; + const auto index = int(ranges::find( + *list, + id, + &Data::ChatFilter::id + ) - begin(*list)); if (index == list->size() && id != 0) { return false; } const auto changed = index + (isNext ? 1 : -1); - if (changed >= int(list->size()) || changed < -1) { + if (changed >= int(list->size()) || changed < 0) { return false; } _controller->setActiveChatsFilter((changed >= 0) diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index 0a8bfcab92..044fce0cca 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -424,7 +424,9 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { }; const auto &list = session->data().chatsFilters().list(); for (const auto &filter : list) { - addFilter(filter); + if (filter.id()) { + addFilter(filter); + } } AddButton( diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index ee938bbb68..4a3f9005a6 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -289,7 +289,7 @@ void SetupSections( st::settingsButton, { &st::settingsIconFolders, kIconDarkBlue })) )->setDuration(0); - if (!controller->session().data().chatsFilters().list().empty() + if (controller->session().data().chatsFilters().has() || controller->session().settings().dialogsFiltersEnabled()) { slided->show(anim::type::instant); preload(); diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 3ffec6c4a3..db77188352 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -16,6 +16,7 @@ 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_user.h" #include "lang/lang_keys.h" #include "ui/filter_icons.h" #include "ui/wrap/vertical_layout_reorder.h" @@ -103,9 +104,16 @@ void FiltersMenu::setup() { _container->move(0, 0); }, _outer.lifetime()); + auto premium = _session->session().user()->flagsValue( + ) | rpl::filter([=](UserData::Flags::Change change) { + return (change.diff & UserDataFlag::Premium); + }) | rpl::map([=] { + return _session->session().user()->isPremium(); + }); const auto filters = &_session->session().data().chatsFilters(); - rpl::single(rpl::empty) | rpl::then( - filters->changed() + rpl::combine( + rpl::single(rpl::empty) | rpl::then(filters->changed()), + std::move(premium) ) | rpl::start_with_next([=] { refresh(); }, _outer.lifetime()); @@ -118,7 +126,7 @@ void FiltersMenu::setup() { const auto i = _filters.find(_activeFilterId); if (i != end(_filters)) { i->second->setActive(false); - } else if (!_activeFilterId) { + } else if (!_activeFilterId && _all) { _all->setActive(false); } _activeFilterId = id; @@ -126,7 +134,7 @@ void FiltersMenu::setup() { if (j != end(_filters)) { j->second->setActive(true); scrollToButton(j->second); - } else if (!_activeFilterId) { + } else if (!_activeFilterId && _all) { _all->setActive(true); scrollToButton(_all); } @@ -178,17 +186,24 @@ void FiltersMenu::scrollToButton(not_null widget) { void FiltersMenu::refresh() { const auto filters = &_session->session().data().chatsFilters(); - if (filters->list().empty() || _ignoreRefresh) { + if (!filters->has() || _ignoreRefresh) { return; } const auto oldTop = _scroll.scrollTop(); - + const auto reorderAll = premium(); if (!_list) { setupList(); + } else if (reorderAll && _all) { + _all = nullptr; + } else if (!reorderAll && !_all) { + _all = prepareAll(); } _reorder->cancel(); auto now = base::flat_map>(); for (const auto &filter : filters->list()) { + if (!reorderAll && !filter.id()) { + continue; + } now.emplace( filter.id(), prepareButton( @@ -206,15 +221,14 @@ void FiltersMenu::refresh() { // so we have to restore it. _scroll.scrollToY(oldTop); const auto i = _filters.find(_activeFilterId); - scrollToButton((i != end(_filters)) ? i->second : _all); + const auto button = ((i != end(_filters)) ? i->second : _all).get(); + if (button) { + scrollToButton(button); + } } void FiltersMenu::setupList() { - _all = prepareButton( - _container, - 0, - tr::lng_filters_all(tr::now), - Ui::FilterIcon::All); + _all = premium() ? nullptr : prepareAll(); _list = _container->add(object_ptr(_container)); _setup = prepareButton( _container, @@ -239,18 +253,32 @@ void FiltersMenu::setupList() { }, _outer.lifetime()); } +bool FiltersMenu::premium() const { + return _session->session().user()->isPremium(); +} + +base::unique_qptr FiltersMenu::prepareAll() { + return prepareButton(_container, 0, {}, Ui::FilterIcon::All, true); +} + base::unique_qptr FiltersMenu::prepareButton( not_null container, FilterId id, const QString &title, - Ui::FilterIcon icon) { - auto button = base::unique_qptr(container->add( - object_ptr( - container, - title, - st::windowFiltersButton))); + Ui::FilterIcon icon, + bool toBeginning) { + auto prepared = object_ptr( + container, + id ? title : tr::lng_filters_all(tr::now), + st::windowFiltersButton); + auto added = toBeginning + ? container->insert(0, std::move(prepared)) + : container->add(std::move(prepared)); + auto button = base::unique_qptr(std::move(added)); const auto raw = button.get(); - const auto &icons = Ui::LookupFilterIcon(icon); + const auto &icons = Ui::LookupFilterIcon(id + ? icon + : Ui::FilterIcon::All); raw->setIconOverride(icons.normal, icons.active); if (id >= 0) { UnreadStateValue( @@ -372,6 +400,13 @@ void FiltersMenu::applyReorder( const auto filters = &_session->session().data().chatsFilters(); const auto &list = filters->list(); + if (_all) { + if (list[0].id() != FilterId()) { + filters->moveAllToFront(); + } + ++oldPosition; + ++newPosition; + } Assert(oldPosition >= 0 && oldPosition < list.size()); Assert(newPosition >= 0 && newPosition < list.size()); const auto id = list[oldPosition].id(); diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index 113a3a25f1..273f51d199 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -41,11 +41,14 @@ private: not_null widget, int oldPosition, int newPosition); + [[nodiscard]] bool premium() const; + [[nodiscard]] base::unique_qptr prepareAll(); [[nodiscard]] base::unique_qptr prepareButton( not_null container, FilterId id, const QString &title, - Ui::FilterIcon icon); + Ui::FilterIcon icon, + bool toBeginning = false); void setupMainMenuIcon(); void showMenu(QPoint position, FilterId id); void showEditBox(FilterId id); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 62c7416c16..2181c1cc21 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -419,7 +419,7 @@ void Filler::addInfo() { void Filler::addToggleFolder() { const auto controller = _controller; const auto history = _request.key.history(); - if (!history || history->owner().chatsFilters().list().empty()) { + if (!history || !history->owner().chatsFilters().has()) { return; } _addAction(PeerMenuCallback::Args{ diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 776115b3ab..d70348298b 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -764,7 +764,7 @@ void SessionController::toggleFiltersMenu(bool enabled) { } void SessionController::refreshFiltersMenu() { - toggleFiltersMenu(!session().data().chatsFilters().list().empty()); + toggleFiltersMenu(session().data().chatsFilters().has()); } rpl::producer<> SessionController::filtersMenuChanged() const {