Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e2e510d8a | ||
|
|
1ed34c6fa0 | ||
|
|
78a0fa55b5 | ||
|
|
d37c040b36 | ||
|
|
e56bbf557d | ||
|
|
5abecec478 | ||
|
|
ccb41f778e | ||
|
|
059a4cf0d8 | ||
|
|
7a535a4554 | ||
|
|
f89167ef94 | ||
|
|
a77777f509 | ||
|
|
4a327ba584 | ||
|
|
a41e9bf67e | ||
|
|
6716973ce0 | ||
|
|
7cc81393d6 | ||
|
|
3e413a036f | ||
|
|
63a8fe7ee8 | ||
|
|
146409844d | ||
|
|
ba0da9f59e | ||
|
|
81aef519d4 | ||
|
|
bcd84518d1 | ||
|
|
f205952ff2 | ||
|
|
1d7622e0b5 | ||
|
|
4d9112283d | ||
|
|
dc49c788a8 | ||
|
|
36741ab780 | ||
|
|
53dffc5e88 | ||
|
|
607c7e7777 | ||
|
|
6f09e1699f | ||
|
|
8c35de48f3 | ||
|
|
b83d943841 | ||
|
|
b11b5caeb3 | ||
|
|
36924da59a | ||
|
|
f0a2c47613 | ||
|
|
5a4449f1a2 | ||
|
|
de3d7a7774 | ||
|
|
b06dbd1c00 | ||
|
|
1fa5e424e9 | ||
|
|
d81c7abf1a | ||
|
|
932215c91d | ||
|
|
7aa1141ba5 | ||
|
|
3699439506 | ||
|
|
76b1288f77 | ||
|
|
8fd9ae4e59 | ||
|
|
53abd2fe38 | ||
|
|
da8a4ba8ab | ||
|
|
9c3990c0c1 | ||
|
|
1eeb46d5fc |
@@ -112,6 +112,8 @@ PRIVATE
|
||||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_filters_remove_manager.cpp
|
||||
api/api_chat_filters_remove_manager.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_chat_links.cpp
|
||||
@@ -636,6 +638,8 @@ PRIVATE
|
||||
data/data_thread.h
|
||||
data/data_types.cpp
|
||||
data/data_types.h
|
||||
data/data_unread_value.cpp
|
||||
data/data_unread_value.h
|
||||
data/data_user.cpp
|
||||
data/data_user.h
|
||||
data/data_user_photos.cpp
|
||||
@@ -1537,6 +1541,8 @@ PRIVATE
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/widgets/chat_filters_tabs_strip.cpp
|
||||
ui/widgets/chat_filters_tabs_strip.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
|
||||
@@ -5099,6 +5099,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_toast_add" = "{chat} added to {folder} folder";
|
||||
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
|
||||
"lng_filters_shareable_status" = "shareable folder";
|
||||
"lng_filters_view_subtitle" = "Tabs view";
|
||||
"lng_filters_vertical" = "Tabs on the left";
|
||||
"lng_filters_horizontal" = "Tabs at the top";
|
||||
|
||||
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
|
||||
"lng_filters_link" = "Share Folder";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.7.2.0" />
|
||||
Version="5.7.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,7,2,0
|
||||
PRODUCTVERSION 5,7,2,0
|
||||
FILEVERSION 5,7,3,0
|
||||
PRODUCTVERSION 5,7,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.7.2.0"
|
||||
VALUE "FileVersion", "5.7.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.2.0"
|
||||
VALUE "ProductVersion", "5.7.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,7,2,0
|
||||
PRODUCTVERSION 5,7,2,0
|
||||
FILEVERSION 5,7,3,0
|
||||
PRODUCTVERSION 5,7,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.7.2.0"
|
||||
VALUE "FileVersion", "5.7.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.2.0"
|
||||
VALUE "ProductVersion", "5.7.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -152,6 +153,7 @@ void InitFilterLinkHeader(
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
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 "api/api_chat_filters_remove_manager.h"
|
||||
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
void RemoveChatFilter(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId,
|
||||
std::vector<not_null<PeerData*>> leave) {
|
||||
const auto api = &session->api();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()));
|
||||
if (leave.empty()) {
|
||||
api->request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()
|
||||
)).send();
|
||||
} else {
|
||||
api->request(MTPchatlists_LeaveChatlist(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(ranges::views::all(
|
||||
leave
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
}) | ranges::to<QVector<MTPInputPeer>>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
|
||||
|
||||
void RemoveComplexChatFilter::request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id) {
|
||||
const auto session = &weak->session();
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
|
||||
const auto has = filter.hasMyLinks();
|
||||
const auto confirm = [=](Fn<void()> action, bool onlyWhenHas = false) {
|
||||
if (!has && onlyWhenHas) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
weak->window().show(Ui::MakeConfirmBox({
|
||||
.text = (has
|
||||
? tr::lng_filters_delete_sure()
|
||||
: tr::lng_filters_remove_sure()),
|
||||
.confirmed = [=](Fn<void()> &&close) { close(); action(); },
|
||||
.confirmText = (has
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_filters_remove_yes()),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
const auto simple = [=] {
|
||||
confirm([=] { RemoveChatFilter(session, id, {}); });
|
||||
};
|
||||
const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
|
||||
if (suggestRemoving.empty()) {
|
||||
simple();
|
||||
return;
|
||||
} else if (_removingRequestId) {
|
||||
if (_removingId == id) {
|
||||
return;
|
||||
}
|
||||
session->api().request(_removingRequestId).cancel();
|
||||
}
|
||||
_removingId = id;
|
||||
_removingRequestId = session->api().request(
|
||||
MTPchatlists_GetLeaveChatlistSuggestions(
|
||||
MTP_inputChatlistDialogFilter(
|
||||
MTP_int(id)))
|
||||
).done(crl::guard(widget, [=, this](const MTPVector<MTPPeer> &result) {
|
||||
_removingRequestId = 0;
|
||||
const auto suggestRemovePeers = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([=](const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto chosen = crl::guard(widget, [=](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
RemoveChatFilter(session, id, std::move(peers));
|
||||
});
|
||||
confirm(crl::guard(widget, [=] {
|
||||
Api::ProcessFilterRemove(
|
||||
weak,
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
suggestRemoving,
|
||||
suggestRemovePeers,
|
||||
chosen);
|
||||
}), true);
|
||||
})).fail(crl::guard(widget, [=, this] {
|
||||
_removingRequestId = 0;
|
||||
simple();
|
||||
})).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
|
||||
class RemoveComplexChatFilter final {
|
||||
public:
|
||||
RemoveComplexChatFilter();
|
||||
|
||||
void request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id);
|
||||
|
||||
private:
|
||||
FilterId _removingId = 0;
|
||||
mtpRequestId _removingRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
content()->selectSkipPage(height(), 1);
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
content()->selectSkipPage(height(), -1);
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
} else if (e->key() == Qt::Key_Escape
|
||||
&& _select
|
||||
&& !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
@@ -215,7 +217,16 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
content()->searchQueryChanged(query);
|
||||
const auto isEmpty = content()->searchQueryChanged(query);
|
||||
if (_specialTabsMode.enabled) {
|
||||
_specialTabsMode.searchIsActive = !isEmpty;
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -543,6 +554,19 @@ auto PeerListBox::collectSelectedRows()
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
|
||||
return _select ? _select->heightValue() : rpl::single(0);
|
||||
}
|
||||
|
||||
void PeerListBox::setSpecialTabMode(bool value) {
|
||||
content()->setIgnoreHiddenRowsOnSearch(value);
|
||||
if (value) {
|
||||
_specialTabsMode.enabled = true;
|
||||
} else {
|
||||
_specialTabsMode = {};
|
||||
}
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer, peer->id.value) {
|
||||
}
|
||||
@@ -1385,10 +1409,12 @@ int PeerListContent::labelHeight() const {
|
||||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2050,13 +2076,16 @@ void PeerListContent::checkScrollForPreload() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
}
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
@@ -2104,6 +2133,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
@@ -2192,6 +2222,10 @@ void PeerListContent::dragLeft() {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
|
||||
_ignoreHiddenRowsOnSearch = value;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -652,12 +652,15 @@ public:
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
void setIgnoreHiddenRowsOnSearch(bool value);
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -879,6 +882,7 @@ private:
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
bool _ignoreHiddenRowsOnSearch = false;
|
||||
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
||||
@@ -1102,6 +1106,9 @@ public:
|
||||
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
|
||||
|
||||
void setSpecialTabMode(bool value);
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
@@ -1168,4 +1175,11 @@ private:
|
||||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
||||
@@ -79,7 +79,6 @@ void ShowReportMessageBox(
|
||||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
@@ -199,6 +198,7 @@ void ShowReportMessageBox(
|
||||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
@@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/share_message_phrase_factory.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -81,11 +83,14 @@ public:
|
||||
void activateSkipColumn(int direction);
|
||||
void activateSkipPage(int pageHeight, int direction);
|
||||
void updateFilter(QString filter = QString());
|
||||
[[nodiscard]] bool isFilterEmpty() const;
|
||||
void selectActive();
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<> searchRequests() const;
|
||||
|
||||
void applyChatFilter(FilterId id);
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
@@ -166,7 +171,9 @@ private:
|
||||
int _upon = -1;
|
||||
int _visibleTop = 0;
|
||||
|
||||
std::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _defaultChatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _customChatsIndexed;
|
||||
not_null<Dialogs::IndexedList*> _chatsIndexed;
|
||||
QString _filter;
|
||||
std::vector<not_null<Dialogs::Row*>> _filtered;
|
||||
|
||||
@@ -282,6 +289,10 @@ void ShareBox::prepare() {
|
||||
|
||||
_select->setQueryChangedCallback([=](const QString &query) {
|
||||
applyFilterUpdate(query);
|
||||
if (_chatsFilters) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}
|
||||
});
|
||||
_select->setItemRemovedCallback([=](uint64 itemId) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
|
||||
@@ -337,10 +348,32 @@ void ShareBox::prepare() {
|
||||
{ .suggestCustomEmoji = true });
|
||||
|
||||
_select->raise();
|
||||
|
||||
{
|
||||
const auto chatsFilters = AddChatFiltersTabsStrip(
|
||||
this,
|
||||
_descriptor.session,
|
||||
[this](FilterId id) {
|
||||
_inner->applyChatFilter(id);
|
||||
scrollToY(0);
|
||||
});
|
||||
chatsFilters->lower();
|
||||
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}, lifetime());
|
||||
_select->heightValue() | rpl::start_with_next([=](int h) {
|
||||
chatsFilters->moveToLeft(0, h);
|
||||
}, chatsFilters->lifetime());
|
||||
_chatsFilters = chatsFilters;
|
||||
}
|
||||
}
|
||||
|
||||
int ShareBox::getTopScrollSkip() const {
|
||||
return _select->isHidden() ? 0 : _select->height();
|
||||
return (_select->isHidden() ? 0 : _select->height())
|
||||
+ ((_chatsFilters && _inner && _inner->isFilterEmpty())
|
||||
? _chatsFilters->height()
|
||||
: 0);
|
||||
}
|
||||
|
||||
int ShareBox::getBottomScrollSkip() const {
|
||||
@@ -671,9 +704,10 @@ ShareBox::Inner::Inner(
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _chatsIndexed(
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add)) {
|
||||
Dialogs::SortMode::Add))
|
||||
, _chatsIndexed(_defaultChatsIndexed.get()) {
|
||||
_rowsTop = st::shareRowsTop;
|
||||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
@@ -691,7 +725,7 @@ ShareBox::Inner::Inner(
|
||||
const auto self = _descriptor.session->user();
|
||||
const auto selfHistory = self->owner().history(self);
|
||||
if (_descriptor.filterCallback(selfHistory)) {
|
||||
_chatsIndexed->addToEnd(selfHistory);
|
||||
_defaultChatsIndexed->addToEnd(selfHistory);
|
||||
}
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
@@ -699,7 +733,7 @@ ShareBox::Inner::Inner(
|
||||
if (!history->peer->isSelf()
|
||||
&& (history->asForum()
|
||||
|| _descriptor.filterCallback(history))) {
|
||||
_chatsIndexed->addToEnd(history);
|
||||
_defaultChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -722,7 +756,7 @@ ShareBox::Inner::Inner(
|
||||
|
||||
_descriptor.session->changes().realtimeNameUpdates(
|
||||
) | rpl::start_with_next([=](const Data::NameUpdate &update) {
|
||||
_chatsIndexed->peerNameChanged(
|
||||
_defaultChatsIndexed->peerNameChanged(
|
||||
update.peer,
|
||||
update.oldFirstLetters);
|
||||
}, lifetime());
|
||||
@@ -1331,6 +1365,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ShareBox::Inner::isFilterEmpty() const {
|
||||
return _filter.isEmpty();
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
@@ -1339,6 +1377,29 @@ rpl::producer<> ShareBox::Inner::searchRequests() const {
|
||||
return _searchRequests.events();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::applyChatFilter(FilterId id) {
|
||||
if (!id) {
|
||||
_chatsIndexed = _defaultChatsIndexed.get();
|
||||
} else {
|
||||
_customChatsIndexed = std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add);
|
||||
_chatsIndexed = _customChatsIndexed.get();
|
||||
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
if (const auto history = row->history()) {
|
||||
if (_descriptor.filterCallback(history)) {
|
||||
_customChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto &data = _descriptor.session->data();
|
||||
addList(data.chatsFilters().chatsList(id)->indexed());
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::peopleReceived(
|
||||
const QString &query,
|
||||
const QVector<MTPPeer> &my,
|
||||
|
||||
@@ -174,6 +174,8 @@ private:
|
||||
bool _peopleFull = false;
|
||||
mtpRequestId _peopleRequest = 0;
|
||||
|
||||
RpWidget *_chatsFilters = nullptr;
|
||||
|
||||
using PeopleCache = QMap<QString, MTPcontacts_Found>;
|
||||
PeopleCache _peopleCache;
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ base::options::toggle TabbedPanelShowOnClick({
|
||||
|
||||
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
|
||||
|
||||
bool ShowPanelOnClick() {
|
||||
return TabbedPanelShowOnClick.value();
|
||||
}
|
||||
|
||||
TabbedPanel::TabbedPanel(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace ChatHelpers {
|
||||
class TabbedSelector;
|
||||
|
||||
extern const char kOptionTabbedPanelShowOnClick[];
|
||||
[[nodiscard]] bool ShowPanelOnClick();
|
||||
|
||||
struct TabbedPanelDescriptor {
|
||||
Window::SessionController *regularWindow = nullptr;
|
||||
|
||||
@@ -396,7 +396,7 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0)
|
||||
<< qint32(0) // Old IV zoom
|
||||
<< qint32(_chatFiltersHorizontal.current() ? 1 : 0)
|
||||
<< qint32(_skipToastsInFocus ? 1 : 0)
|
||||
<< qint32(_recordVideoMessages ? 1 : 0)
|
||||
<< SerializeVideoQuality(_videoQuality)
|
||||
@@ -528,6 +528,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0;
|
||||
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
|
||||
quint32 videoQuality = SerializeVideoQuality(_videoQuality);
|
||||
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -838,8 +839,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
stream >> includeMutedCounterFolders;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
qint32 oldIvZoom = 0;
|
||||
stream >> oldIvZoom;
|
||||
stream >> chatFiltersHorizontal;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> skipToastsInFocus;
|
||||
@@ -1068,6 +1068,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_skipToastsInFocus = (skipToastsInFocus == 1);
|
||||
_recordVideoMessages = (recordVideoMessages == 1);
|
||||
_videoQuality = DeserializeVideoQuality(videoQuality);
|
||||
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -1459,6 +1460,7 @@ void Settings::resetOnLastLogout() {
|
||||
_ivZoom = 100;
|
||||
_recordVideoMessages = false;
|
||||
_videoQuality = {};
|
||||
_chatFiltersHorizontal = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
@@ -1634,4 +1636,16 @@ void Settings::setVideoQuality(Media::VideoQuality value) {
|
||||
_videoQuality = value;
|
||||
}
|
||||
|
||||
bool Settings::chatFiltersHorizontal() const {
|
||||
return _chatFiltersHorizontal.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> Settings::chatFiltersHorizontalChanges() const {
|
||||
return _chatFiltersHorizontal.changes();
|
||||
}
|
||||
|
||||
void Settings::setChatFiltersHorizontal(bool value) {
|
||||
_chatFiltersHorizontal = value;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -929,6 +929,10 @@ public:
|
||||
[[nodiscard]] rpl::producer<int> ivZoomValue() const;
|
||||
void setIvZoom(int value);
|
||||
|
||||
[[nodiscard]] bool chatFiltersHorizontal() const;
|
||||
[[nodiscard]] rpl::producer<bool> chatFiltersHorizontalChanges() const;
|
||||
void setChatFiltersHorizontal(bool value);
|
||||
|
||||
[[nodiscard]] Media::VideoQuality videoQuality() const;
|
||||
void setVideoQuality(Media::VideoQuality quality);
|
||||
|
||||
@@ -1070,6 +1074,7 @@ private:
|
||||
QByteArray _tonsiteStorageToken;
|
||||
rpl::variable<int> _ivZoom = 100;
|
||||
Media::VideoQuality _videoQuality;
|
||||
rpl::variable<bool> _chatFiltersHorizontal = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 5007002;
|
||||
constexpr auto AppVersionStr = "5.7.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 5007003;
|
||||
constexpr auto AppVersionStr = "5.7.3";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -74,6 +74,9 @@ void SponsoredMessages::clearOldRequests() {
|
||||
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
if (isTopBarFor(history)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
|
||||
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
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 "data/data_unread_value.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> MainListUnreadState(
|
||||
not_null<Dialogs::MainList*> list) {
|
||||
return rpl::single(rpl::empty) | rpl::then(
|
||||
list->unreadStateChanges() | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
return list->unreadState();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state) {
|
||||
const auto folderId = Data::Folder::kId;
|
||||
if (const auto folder = session->data().folderLoaded(folderId)) {
|
||||
return state - folder->chatsList()->unreadState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId) {
|
||||
if (filterId > 0) {
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
return MainListUnreadState(filters->chatsList(filterId));
|
||||
}
|
||||
return MainListUnreadState(
|
||||
session->data().chatsList()
|
||||
) | rpl::map([=](const Dialogs::UnreadState &state) {
|
||||
return MainListMapUnreadState(session, state);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<bool> IncludeMutedCounterFoldersValue() {
|
||||
using namespace Window::Notifications;
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
Core::App().notifications().settingsChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == ChangeType::IncludeMuted
|
||||
) | rpl::to_empty
|
||||
) | rpl::map([] {
|
||||
return Core::App().settings().includeMutedCounterFolders();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 Dialogs {
|
||||
struct UnreadState;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state);
|
||||
|
||||
[[nodiscard]] rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> IncludeMutedCounterFoldersValue();
|
||||
|
||||
} // namespace Data
|
||||
@@ -11,13 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/ui/chat_search_empty.h"
|
||||
#include "dialogs/ui/chat_search_in.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_video_userpic.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "dialogs/dialogs_search_tags.h"
|
||||
#include "history/view/history_view_chat_preview.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -44,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
@@ -68,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/peers/edit_forum_topic_box.h"
|
||||
|
||||
@@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_inner_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_top_bar_widget.h"
|
||||
@@ -26,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
@@ -46,14 +46,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session_settings.h"
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_slide_animation.h"
|
||||
@@ -655,6 +651,11 @@ Widget::Widget(
|
||||
setupMoreChatsBar();
|
||||
setupDownloadBar();
|
||||
}
|
||||
|
||||
if (session().settings().dialogsFiltersEnabled()
|
||||
&& Core::App().settings().chatFiltersHorizontal()) {
|
||||
toggleFiltersMenu(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::chosenRow(const ChosenRow &row) {
|
||||
@@ -1227,6 +1228,9 @@ void Widget::updateControlsVisibility(bool fast) {
|
||||
if (_moreChatsBar) {
|
||||
_moreChatsBar->show();
|
||||
}
|
||||
if (_chatFilters) {
|
||||
_chatFilters->show();
|
||||
}
|
||||
if (_openedFolder || _openedForum) {
|
||||
_subsectionTopBar->show();
|
||||
if (_forumTopShadow) {
|
||||
@@ -1296,6 +1300,41 @@ void Widget::updateHasFocus(not_null<QWidget*> focused) {
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::toggleFiltersMenu(bool enabled) {
|
||||
if (!enabled == !_chatFilters) {
|
||||
return;
|
||||
} else if (enabled) {
|
||||
_chatFilters = base::make_unique_q<Ui::RpWidget>(this);
|
||||
const auto raw = _chatFilters.get();
|
||||
const auto inner = Ui::AddChatFiltersTabsStrip(
|
||||
_chatFilters.get(),
|
||||
&session(),
|
||||
[this](FilterId id) {
|
||||
if (controller()->activeChatsFilterCurrent() != id) {
|
||||
controller()->setActiveChatsFilter(id);
|
||||
}
|
||||
},
|
||||
true);
|
||||
raw->show();
|
||||
raw->stackUnder(_scroll);
|
||||
raw->resizeToWidth(width());
|
||||
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(raw);
|
||||
shadow->show();
|
||||
inner->sizeValue() | rpl::start_with_next([=, this](const QSize &s) {
|
||||
raw->resize(s);
|
||||
shadow->setGeometry(
|
||||
0,
|
||||
s.height() - shadow->height(),
|
||||
s.width(),
|
||||
shadow->height());
|
||||
updateControlsGeometry();
|
||||
}, _chatFilters->lifetime());
|
||||
updateControlsGeometry();
|
||||
} else {
|
||||
_chatFilters = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Widget::cancelSearchByMouseBack() {
|
||||
return _searchHasFocus
|
||||
&& !_searchSuggestionsLocked
|
||||
@@ -1845,6 +1884,12 @@ void Widget::scrollToDefault(bool verytop) {
|
||||
this,
|
||||
QPoint(),
|
||||
QRect(0, top, wideGeometry.width(), skip));
|
||||
if (_chatFilters) {
|
||||
Ui::RenderWidget(
|
||||
p,
|
||||
_chatFilters,
|
||||
QPoint(0, skip - _chatFilters->height()));
|
||||
}
|
||||
Ui::RenderWidget(p, _scroll, QPoint(0, skip));
|
||||
}
|
||||
if (scrollGeometry != wideGeometry) {
|
||||
@@ -1860,6 +1905,9 @@ void Widget::startWidthAnimation() {
|
||||
}
|
||||
_widthAnimationCache = grabNonNarrowScrollFrame();
|
||||
_scroll->hide();
|
||||
if (_chatFilters) {
|
||||
_chatFilters->hide();
|
||||
}
|
||||
updateStoriesVisibility();
|
||||
}
|
||||
|
||||
@@ -1867,6 +1915,9 @@ void Widget::stopWidthAnimation() {
|
||||
_widthAnimationCache = QPixmap();
|
||||
if (!_showAnimation) {
|
||||
_scroll->setVisible(!_suggestions);
|
||||
if (_chatFilters) {
|
||||
_chatFilters->setVisible(!_suggestions);
|
||||
}
|
||||
}
|
||||
updateStoriesVisibility();
|
||||
update();
|
||||
@@ -1961,6 +2012,9 @@ void Widget::startSlideAnimation(
|
||||
if (_moreChatsBar) {
|
||||
_moreChatsBar->hide();
|
||||
}
|
||||
if (_chatFilters) {
|
||||
_chatFilters->hide();
|
||||
}
|
||||
if (_forumTopShadow) {
|
||||
_forumTopShadow->hide();
|
||||
}
|
||||
@@ -3085,6 +3139,9 @@ bool Widget::applySearchState(SearchState state) {
|
||||
const auto tagsChanged = (_searchState.tags != state.tags);
|
||||
const auto queryChanged = (_searchState.query != state.query);
|
||||
const auto tabChanged = (_searchState.tab != state.tab);
|
||||
const auto queryEmptyChanged = queryChanged
|
||||
? (_searchState.query.isEmpty() != state.query.isEmpty())
|
||||
: false;
|
||||
|
||||
if (forum) {
|
||||
if (_openedForum == forum) {
|
||||
@@ -3120,6 +3177,10 @@ bool Widget::applySearchState(SearchState state) {
|
||||
? peer->owner().history(migrateFrom).get()
|
||||
: nullptr;
|
||||
_searchState = state;
|
||||
if (_chatFilters && queryEmptyChanged) {
|
||||
_chatFilters->setVisible(_searchState.query.isEmpty());
|
||||
updateControlsGeometry();
|
||||
}
|
||||
_searchWithPostsPreview = computeSearchWithPostsPreview();
|
||||
if (queryChanged) {
|
||||
updateLockUnlockVisibility(anim::type::normal);
|
||||
@@ -3507,6 +3568,9 @@ void Widget::updateControlsGeometry() {
|
||||
if (_forumRequestsBar) {
|
||||
_forumRequestsBar->resizeToWidth(barw);
|
||||
}
|
||||
if (_chatFilters) {
|
||||
_chatFilters->resizeToWidth(barw);
|
||||
}
|
||||
_updateScrollGeometryCached = [=] {
|
||||
const auto moreChatsBarTop = expandedStoriesTop
|
||||
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
|
||||
@@ -3528,8 +3592,15 @@ void Widget::updateControlsGeometry() {
|
||||
if (_forumReportBar) {
|
||||
_forumReportBar->bar().move(0, forumReportTop);
|
||||
}
|
||||
const auto scrollTop = forumReportTop
|
||||
const auto chatFiltersTop = forumReportTop
|
||||
+ (_forumReportBar ? _forumReportBar->bar().height() : 0);
|
||||
if (_chatFilters) {
|
||||
_chatFilters->move(0, chatFiltersTop);
|
||||
}
|
||||
const auto scrollTop = chatFiltersTop
|
||||
+ ((_chatFilters && _searchState.query.isEmpty())
|
||||
? (_chatFilters->height() * (1. - narrowRatio))
|
||||
: 0);
|
||||
const auto scrollHeight = height() - scrollTop - bottomSkip;
|
||||
const auto wasScrollHeight = _scroll->height();
|
||||
_scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
|
||||
|
||||
@@ -129,6 +129,7 @@ public:
|
||||
[[nodiscard]] RowDescriptor resolveChatNext(RowDescriptor from = {}) const;
|
||||
[[nodiscard]] RowDescriptor resolveChatPrevious(RowDescriptor from = {}) const;
|
||||
void updateHasFocus(not_null<QWidget*> focused);
|
||||
void toggleFiltersMenu(bool value);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e) override;
|
||||
@@ -317,6 +318,8 @@ private:
|
||||
std::unique_ptr<Ui::RequestsBar> _forumRequestsBar;
|
||||
std::unique_ptr<HistoryView::ContactStatus> _forumReportBar;
|
||||
|
||||
base::unique_qptr<Ui::RpWidget> _chatFilters;
|
||||
|
||||
object_ptr<Ui::ElasticScroll> _scroll;
|
||||
QPointer<InnerWidget> _inner;
|
||||
std::unique_ptr<Suggestions> _suggestions;
|
||||
|
||||
@@ -666,7 +666,8 @@ bool InnerWidget::elementUnderCursor(
|
||||
return (Element::Hovered() == view);
|
||||
}
|
||||
|
||||
HistoryView::SelectionModeResult InnerWidget::elementInSelectionMode() {
|
||||
HistoryView::SelectionModeResult InnerWidget::elementInSelectionMode(
|
||||
const HistoryView::Element *) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,8 @@ public:
|
||||
HistoryView::Context elementContext() override;
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
HistoryView::SelectionModeResult elementInSelectionMode() override;
|
||||
HistoryView::SelectionModeResult elementInSelectionMode(
|
||||
const HistoryView::Element *view) override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
int from,
|
||||
|
||||
@@ -155,7 +155,11 @@ public:
|
||||
not_null<const Element*> view) override {
|
||||
return (Element::Moused() == view);
|
||||
}
|
||||
HistoryView::SelectionModeResult elementInSelectionMode() override {
|
||||
HistoryView::SelectionModeResult elementInSelectionMode(
|
||||
const Element *view) override {
|
||||
if (view && view->data()->isSponsored()) {
|
||||
return HistoryView::SelectionModeResult();
|
||||
}
|
||||
return _widget
|
||||
? _widget->inSelectionMode()
|
||||
: HistoryView::SelectionModeResult();
|
||||
|
||||
@@ -198,7 +198,8 @@ void SetupSwipeHandler(
|
||||
const auto &touches = t->touchPoints();
|
||||
const auto released = [&](int index) {
|
||||
return (touches.size() > index)
|
||||
&& (touches.at(index).state() & Qt::TouchPointReleased);
|
||||
&& (int(touches.at(index).state())
|
||||
& int(Qt::TouchPointReleased));
|
||||
};
|
||||
const auto cancel = released(0)
|
||||
|| released(1)
|
||||
|
||||
@@ -421,8 +421,16 @@ HistoryWidget::HistoryWidget(
|
||||
initTabbedSelector();
|
||||
|
||||
_attachToggle->setClickedCallback([=] {
|
||||
const auto toggle = _attachBotsMenu && _attachBotsMenu->isHidden();
|
||||
base::call_delayed(st::historyAttach.ripple.hideDuration, this, [=] {
|
||||
chooseAttach();
|
||||
if (_attachBotsMenu && toggle) {
|
||||
_attachBotsMenu->showAnimated();
|
||||
} else {
|
||||
chooseAttach();
|
||||
if (_attachBotsMenu) {
|
||||
_attachBotsMenu->hideAnimated();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2574,6 +2582,8 @@ void HistoryWidget::showHistory(
|
||||
}
|
||||
}));
|
||||
checkState();
|
||||
} else {
|
||||
requestSponsoredMessageBar();
|
||||
}
|
||||
} else {
|
||||
_chooseForReport = nullptr;
|
||||
@@ -2611,7 +2621,7 @@ void HistoryWidget::setHistory(History *history) {
|
||||
if (was && !now) {
|
||||
_attachToggle->removeEventFilter(_attachBotsMenu.get());
|
||||
_attachBotsMenu->hideFast();
|
||||
} else if (now && !was) {
|
||||
} else if (now && !was && !ChatHelpers::ShowPanelOnClick()) {
|
||||
_attachToggle->installEventFilter(_attachBotsMenu.get());
|
||||
}
|
||||
|
||||
@@ -2699,7 +2709,9 @@ void HistoryWidget::refreshAttachBotsMenu() {
|
||||
}
|
||||
_attachBotsMenu->setOrigin(
|
||||
Ui::PanelAnimation::Origin::BottomLeft);
|
||||
_attachToggle->installEventFilter(_attachBotsMenu.get());
|
||||
if (!ChatHelpers::ShowPanelOnClick()) {
|
||||
_attachToggle->installEventFilter(_attachBotsMenu.get());
|
||||
}
|
||||
_attachBotsMenu->heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
moveFieldControls();
|
||||
@@ -4521,7 +4533,6 @@ void HistoryWidget::showFinished() {
|
||||
_showAnimation = nullptr;
|
||||
doneShow();
|
||||
synteticScrollToY(_scroll->scrollTop());
|
||||
requestSponsoredMessageBar();
|
||||
}
|
||||
|
||||
void HistoryWidget::doneShow() {
|
||||
|
||||
@@ -111,7 +111,8 @@ bool DefaultElementDelegate::elementUnderCursor(
|
||||
return false;
|
||||
}
|
||||
|
||||
SelectionModeResult DefaultElementDelegate::elementInSelectionMode() {
|
||||
SelectionModeResult DefaultElementDelegate::elementInSelectionMode(
|
||||
const Element *view) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ class ElementDelegate {
|
||||
public:
|
||||
virtual Context elementContext() = 0;
|
||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||
virtual SelectionModeResult elementInSelectionMode() = 0;
|
||||
virtual SelectionModeResult elementInSelectionMode(
|
||||
const Element *view) = 0;
|
||||
virtual bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
int from,
|
||||
@@ -136,7 +137,7 @@ public:
|
||||
class DefaultElementDelegate : public ElementDelegate {
|
||||
public:
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
SelectionModeResult elementInSelectionMode() override;
|
||||
SelectionModeResult elementInSelectionMode(const Element *view) override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
int from,
|
||||
|
||||
@@ -1782,7 +1782,11 @@ bool ListWidget::elementUnderCursor(
|
||||
return (_overElement == view);
|
||||
}
|
||||
|
||||
SelectionModeResult ListWidget::elementInSelectionMode() {
|
||||
SelectionModeResult ListWidget::elementInSelectionMode(
|
||||
const HistoryView::Element *view) {
|
||||
if (view && !_delegate->listIsItemGoodForSelection(view->data())) {
|
||||
return {};
|
||||
}
|
||||
return inSelectionMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -391,7 +391,7 @@ public:
|
||||
// ElementDelegate interface.
|
||||
Context elementContext() override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
SelectionModeResult elementInSelectionMode() override;
|
||||
SelectionModeResult elementInSelectionMode(const Element *view) override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
int from,
|
||||
|
||||
@@ -1100,7 +1100,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||
if (hasGesture) {
|
||||
p.translate(context.gestureHorizontal.translation, 0);
|
||||
}
|
||||
const auto selectionModeResult = delegate()->elementInSelectionMode();
|
||||
const auto selectionModeResult = delegate()->elementInSelectionMode(this);
|
||||
const auto selectionTranslation = (selectionModeResult.progress > 0)
|
||||
? (selectionModeResult.progress
|
||||
* AdditionalSpaceForSelectionCheckbox(this, g))
|
||||
@@ -3868,7 +3868,7 @@ bool Message::displayFastReply() const {
|
||||
return hasFastReply()
|
||||
&& data()->isRegular()
|
||||
&& canSendAnything()
|
||||
&& !delegate()->elementInSelectionMode().inSelectionMode;
|
||||
&& !delegate()->elementInSelectionMode(this).inSelectionMode;
|
||||
}
|
||||
|
||||
bool Message::displayRightActionComments() const {
|
||||
@@ -4032,7 +4032,7 @@ void Message::drawRightAction(
|
||||
|
||||
ClickHandlerPtr Message::rightActionLink(
|
||||
std::optional<QPoint> pressPoint) const {
|
||||
if (delegate()->elementInSelectionMode().progress > 0) {
|
||||
if (delegate()->elementInSelectionMode(this).progress > 0) {
|
||||
return nullptr;
|
||||
}
|
||||
ensureRightAction();
|
||||
|
||||
@@ -898,7 +898,7 @@ void RepliesWidget::setupSwipeReply() {
|
||||
}
|
||||
}, [=, show = controller()->uiShow()](int cursorTop) {
|
||||
auto result = HistoryView::SwipeHandlerFinishData();
|
||||
if (_inner->elementInSelectionMode().inSelectionMode) {
|
||||
if (_inner->elementInSelectionMode(nullptr).inSelectionMode) {
|
||||
return result;
|
||||
}
|
||||
const auto view = _inner->lookupItemByY(cursorTop);
|
||||
|
||||
@@ -663,8 +663,8 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
||||
const auto stickerSet = stickerSetData();
|
||||
const auto factcheck = factcheckData();
|
||||
const auto sponsored = sponsoredData();
|
||||
const auto specialRightPix = ((sponsored && !sponsored->hasMedia)
|
||||
|| stickerSet);
|
||||
const auto specialRightPix = (stickerSet
|
||||
|| (sponsored && !sponsored->hasMedia && _data->photo));
|
||||
const auto lineHeight = UnitedLineHeight();
|
||||
const auto factcheckMetrics = factcheck
|
||||
? computeFactcheckMetrics(_description.countHeight(innerWidth))
|
||||
@@ -678,7 +678,7 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
||||
}
|
||||
const auto linesMax = factcheck
|
||||
? (factcheckMetrics.lines + 1)
|
||||
: (specialRightPix || isLogEntryOriginal())
|
||||
: (sponsored || isLogEntryOriginal())
|
||||
? kMaxOriginalEntryLines
|
||||
: 5;
|
||||
const auto siteNameHeight = _siteNameLines ? lineHeight : 0;
|
||||
@@ -711,7 +711,9 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
||||
newHeight += _titleLines * lineHeight;
|
||||
}
|
||||
|
||||
const auto descriptionHeight = _description.countHeight(wleft);
|
||||
const auto descriptionHeight = _description.countHeight(sponsored
|
||||
? innerWidth
|
||||
: wleft);
|
||||
const auto restLines = (linesMax - _siteNameLines - _titleLines);
|
||||
if (descriptionHeight < restLines * descriptionLineHeight) {
|
||||
// We have height for all the lines.
|
||||
@@ -1510,7 +1512,7 @@ void WebPage::clickHandlerPressedChanged(
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (p == _openl) {
|
||||
if ((p == _openl) || (sponsoredData() && sponsoredData()->link == p)) {
|
||||
if (pressed) {
|
||||
if (!_ripple) {
|
||||
const auto full = Rect(currentSize());
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer_rpl.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/local_url_handlers.h"
|
||||
@@ -2039,9 +2040,6 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<Api::SendAction()> actionFactory,
|
||||
Fn<void(bool)> attach) {
|
||||
if (!Data::CanSend(peer, ChatRestriction::SendInline)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Ui::DropdownMenu>(
|
||||
parent,
|
||||
st::dropdownMenuWithIcons);
|
||||
@@ -2096,8 +2094,10 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
||||
ChooseAndSendLocation(controller, config, actionFactory());
|
||||
}, &st::menuIconAddress);
|
||||
}
|
||||
const auto addBots = Data::CanSend(peer, ChatRestriction::SendInline);
|
||||
for (const auto &bot : bots->attachBots()) {
|
||||
if (!bot.inAttachMenu
|
||||
if (!addBots
|
||||
|| !bot.inAttachMenu
|
||||
|| !PeerMatchesTypes(peer, bot.user, bot.types)) {
|
||||
continue;
|
||||
}
|
||||
@@ -2128,7 +2128,11 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
||||
}, action->lifetime());
|
||||
raw->addAction(std::move(action));
|
||||
}
|
||||
if (raw->actions().size() <= minimal) {
|
||||
const auto actions = raw->actions().size();
|
||||
const auto onclick = ChatHelpers::ShowPanelOnClick();
|
||||
if (!actions) {
|
||||
return nullptr;
|
||||
} else if (actions <= minimal && !onclick) {
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -1098,6 +1098,12 @@ void MainWidget::dialogsCancelled() {
|
||||
_history->activate();
|
||||
}
|
||||
|
||||
void MainWidget::toggleFiltersMenu(bool value) const {
|
||||
if (_dialogs) {
|
||||
_dialogs->toggleFiltersMenu(value);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::setChatBackground(
|
||||
const Data::WallPaper &background,
|
||||
QImage &&image) {
|
||||
|
||||
@@ -213,6 +213,7 @@ public:
|
||||
void showNonPremiumLimitToast(bool download);
|
||||
|
||||
void dialogsCancelled();
|
||||
void toggleFiltersMenu(bool value) const;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
@@ -1855,44 +1855,48 @@ void AddWithdrawalWidget(
|
||||
}, label->lifetime());
|
||||
|
||||
const auto lockedColor = anim::with_alpha(stButton.textFg->c, .5);
|
||||
const auto lockedLabelTop = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
tr::lng_bot_earn_balance_button_locked(),
|
||||
st::botEarnLockedButtonLabel);
|
||||
lockedLabelTop->setTextColorOverride(lockedColor);
|
||||
lockedLabelTop->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
const auto lockedLabelBottom = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
QString(),
|
||||
st::botEarnLockedButtonLabel);
|
||||
lockedLabelBottom->setTextColorOverride(lockedColor);
|
||||
lockedLabelBottom->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
const auto lockedLabel = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
lockedLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
struct LockedState final {
|
||||
Ui::Text::String text;
|
||||
bool locked = false;
|
||||
bool dateIsNull = false;
|
||||
rpl::lifetime dateUpdateLifetime;
|
||||
};
|
||||
const auto state = lockedLabel->lifetime().make_state<LockedState>();
|
||||
rpl::combine(
|
||||
rpl::duplicate(lockedValue),
|
||||
button->sizeValue(),
|
||||
lockedLabelTop->sizeValue(),
|
||||
lockedLabelBottom->sizeValue()
|
||||
) | rpl::start_with_next([=](
|
||||
bool locked,
|
||||
const QSize &b,
|
||||
const QSize &top,
|
||||
const QSize &bottom) {
|
||||
const auto factor = locked ? 1 : -10;
|
||||
const auto sumHeight = top.height() + bottom.height();
|
||||
lockedLabelTop->moveToLeft(
|
||||
(b.width() - top.width()) / 2,
|
||||
factor * (b.height() - sumHeight) / 2);
|
||||
lockedLabelBottom->moveToLeft(
|
||||
(b.width() - bottom.width()) / 2,
|
||||
factor * ((b.height() - sumHeight) / 2 + top.height()));
|
||||
}, lockedLabelTop->lifetime());
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](bool locked, const QSize &s) {
|
||||
state->locked = locked;
|
||||
lockedLabel->resize(s);
|
||||
}, lockedLabel->lifetime());
|
||||
lockedLabel->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(lockedLabel);
|
||||
p.setPen(state->locked ? QPen(lockedColor) : stButton.textFg->p);
|
||||
if (state->dateIsNull) {
|
||||
p.setFont(st::channelEarnSemiboldLabel.style.font);
|
||||
p.drawText(
|
||||
lockedLabel->rect(),
|
||||
style::al_center,
|
||||
tr::lng_bot_earn_balance_button_locked(tr::now));
|
||||
return;
|
||||
}
|
||||
state->text.draw(p, {
|
||||
.position = QPoint(
|
||||
0,
|
||||
(lockedLabel->height() - state->text.minHeight()) / 2),
|
||||
.outerWidth = lockedLabel->width(),
|
||||
.availableWidth = lockedLabel->width(),
|
||||
.align = style::al_center,
|
||||
});
|
||||
}, lockedLabel->lifetime());
|
||||
|
||||
const auto dateUpdateLifetime
|
||||
= lockedLabelBottom->lifetime().make_state<rpl::lifetime>();
|
||||
std::move(
|
||||
dateValue
|
||||
) | rpl::start_with_next([=](const QDateTime &dt) {
|
||||
dateUpdateLifetime->destroy();
|
||||
state->dateUpdateLifetime.destroy();
|
||||
state->dateIsNull = dt.isNull();
|
||||
if (dt.isNull()) {
|
||||
return;
|
||||
}
|
||||
@@ -1901,7 +1905,7 @@ void AddWithdrawalWidget(
|
||||
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { lockedLabelBottom->update(); },
|
||||
.customEmojiRepaint = [=] { lockedLabel->update(); },
|
||||
};
|
||||
const auto emoji = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
@@ -1929,11 +1933,18 @@ void AddWithdrawalWidget(
|
||||
: (u"%1:%2"_q)
|
||||
.arg(minutes, 2, 10, kZero)
|
||||
.arg(seconds, 2, 10, kZero);
|
||||
lockedLabelBottom->setMarkedText(
|
||||
base::duplicate(emoji).append(formatted),
|
||||
state->text.setMarkedText(
|
||||
st::botEarnLockedButtonLabel.style,
|
||||
TextWithEntities()
|
||||
.append(tr::lng_bot_earn_balance_button_locked(tr::now))
|
||||
.append('\n')
|
||||
.append(emoji)
|
||||
.append(formatted),
|
||||
kMarkupTextOptions,
|
||||
context);
|
||||
}, *dateUpdateLifetime);
|
||||
}, lockedLabelBottom->lifetime());
|
||||
lockedLabel->update();
|
||||
}, state->dateUpdateLifetime);
|
||||
}, lockedLabel->lifetime());
|
||||
|
||||
Api::HandleWithdrawalButton(
|
||||
Api::RewardReceiver{
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/box_content_divider.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
@@ -843,6 +844,34 @@ void SetupTopContent(
|
||||
|
||||
}
|
||||
|
||||
void SetupView(not_null<Ui::VerticalLayout*> content) {
|
||||
Ui::AddDivider(content);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSubsectionTitle(content, tr::lng_filters_view_subtitle());
|
||||
|
||||
const auto group = std::make_shared<Ui::RadioenumGroup<bool>>(
|
||||
Core::App().settings().chatFiltersHorizontal());
|
||||
const auto addSend = [&](bool value, const QString &text) {
|
||||
content->add(
|
||||
object_ptr<Ui::Radioenum<bool>>(
|
||||
content,
|
||||
group,
|
||||
value,
|
||||
text,
|
||||
st::settingsSendType),
|
||||
st::settingsSendTypePadding);
|
||||
};
|
||||
addSend(false, tr::lng_filters_vertical(tr::now));
|
||||
addSend(true, tr::lng_filters_horizontal(tr::now));
|
||||
|
||||
group->setChangedCallback([=](bool value) {
|
||||
Core::App().settings().setChatFiltersHorizontal(value);
|
||||
Core::App().saveSettingsDelayed();
|
||||
});
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Folders::Folders(
|
||||
@@ -871,6 +900,8 @@ void Folders::setupContent(not_null<Window::SessionController*> controller) {
|
||||
|
||||
_save = SetupFoldersContent(controller, content);
|
||||
|
||||
SetupView(content);
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,8 @@ void Uploader::processDocumentProgress(FullMsgId itemId) {
|
||||
const auto document = media ? media->document() : nullptr;
|
||||
const auto sendAction = (document && document->isVoiceMessage())
|
||||
? Api::SendProgressType::UploadVoice
|
||||
: (document && document->isVideoMessage())
|
||||
? Api::SendProgressType::UploadRound
|
||||
: Api::SendProgressType::UploadFile;
|
||||
const auto progress = (document && document->uploading())
|
||||
? ((document->uploadingData->offset * 100)
|
||||
@@ -250,6 +252,8 @@ void Uploader::processDocumentFailed(FullMsgId itemId) {
|
||||
const auto document = media ? media->document() : nullptr;
|
||||
const auto sendAction = (document && document->isVoiceMessage())
|
||||
? Api::SendProgressType::UploadVoice
|
||||
: (document && document->isVideoMessage())
|
||||
? Api::SendProgressType::UploadRound
|
||||
: Api::SendProgressType::UploadFile;
|
||||
sendProgressUpdate(item, sendAction, -1);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/filter_link_header.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/chat_filters_tabs_slider.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_chat_helpers.h" // defaultEmojiSuggestions
|
||||
#include "styles/style_dialogs.h" // dialogsSearchTabs
|
||||
#include "styles/style_filter_icons.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
@@ -68,6 +72,7 @@ private:
|
||||
|
||||
QString _folderTitle;
|
||||
not_null<const style::icon*> _folderIcon;
|
||||
bool _horizontalFilters = false;
|
||||
|
||||
int _maxHeight = 0;
|
||||
|
||||
@@ -75,6 +80,46 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QImage GeneratePreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QString &title,
|
||||
int badge) {
|
||||
using Tabs = Ui::ChatsFiltersTabs;
|
||||
auto owned = parent->lifetime().make_state<base::unique_qptr<Tabs>>(
|
||||
base::make_unique_q<Tabs>(parent, st::dialogsSearchTabs));
|
||||
const auto raw = owned->get();
|
||||
raw->setSections({
|
||||
tr::lng_filters_name_people(tr::now),
|
||||
title,
|
||||
tr::lng_filters_name_unread(tr::now),
|
||||
});
|
||||
raw->fitWidthToSections();
|
||||
raw->setActiveSectionFast(1);
|
||||
raw->stopAnimation();
|
||||
|
||||
auto result = QImage(
|
||||
raw->size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
result.fill(st::windowBg->c);
|
||||
{
|
||||
auto p = QPainter(&result);
|
||||
Ui::RenderWidget(p, raw, QPoint(), raw->rect());
|
||||
|
||||
const auto &r = st::defaultEmojiSuggestions.fadeRight;
|
||||
const auto &l = st::defaultEmojiSuggestions.fadeLeft;
|
||||
const auto padding = st::filterLinkSubsectionTitlePadding.top();
|
||||
const auto w = raw->width();
|
||||
const auto h = raw->height();
|
||||
r.fill(p, QRect(w - r.width() - padding, 0, r.width(), h));
|
||||
l.fill(p, QRect(padding, 0, l.width(), h));
|
||||
p.fillRect(0, 0, padding, h, st::windowBg);
|
||||
p.fillRect(w - padding, 0, padding, raw->height(), st::windowBg);
|
||||
}
|
||||
owned->reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage GeneratePreview(
|
||||
const QString &title,
|
||||
not_null<const style::icon*> icon,
|
||||
@@ -199,17 +244,18 @@ Widget::Widget(
|
||||
, _titleFont(st::boxTitle.style.font)
|
||||
, _titlePadding(st::filterLinkTitlePadding)
|
||||
, _folderTitle(descriptor.folderTitle)
|
||||
, _folderIcon(descriptor.folderIcon) {
|
||||
, _folderIcon(descriptor.folderIcon)
|
||||
, _horizontalFilters(descriptor.horizontalFilters) {
|
||||
setMinimumHeight(st::boxTitleHeight);
|
||||
refreshTitleText();
|
||||
setTitlePosition(st::boxTitlePosition.x(), st::boxTitlePosition.y());
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([this] {
|
||||
_preview = QImage();
|
||||
}, lifetime());
|
||||
|
||||
_badge.changes() | rpl::start_with_next([=] {
|
||||
_badge.changes() | rpl::start_with_next([this] {
|
||||
_preview = QImage();
|
||||
update();
|
||||
}, lifetime());
|
||||
@@ -242,7 +288,9 @@ void Widget::resizeEvent(QResizeEvent *e) {
|
||||
_about->resizeToWidth(availableWidth);
|
||||
|
||||
const auto minHeight = minimumHeight();
|
||||
const auto maxHeight = st::filterLinkAboutTop
|
||||
const auto maxHeight = (_horizontalFilters
|
||||
? (st::filterLinkAboutTop * 0.8)
|
||||
: st::filterLinkAboutTop)
|
||||
+ _about->height()
|
||||
+ st::filterLinkAboutBottom;
|
||||
if (maxHeight <= minHeight) {
|
||||
@@ -283,12 +331,22 @@ void Widget::resizeEvent(QResizeEvent *e) {
|
||||
QRectF Widget::previewRect(
|
||||
float64 topProgress,
|
||||
float64 sizeProgress) const {
|
||||
const auto size = st::filterLinkPreview * sizeProgress;
|
||||
return QRectF(
|
||||
(width() - size) / 2.,
|
||||
st::filterLinkPreviewTop * topProgress,
|
||||
size,
|
||||
size);
|
||||
if (_horizontalFilters) {
|
||||
const auto size = (_preview.size() / style::DevicePixelRatio())
|
||||
* sizeProgress;
|
||||
return QRectF(
|
||||
(width() - size.width()) / 2.,
|
||||
st::filterLinkPreviewTop * 1.5 * topProgress,
|
||||
size.width(),
|
||||
size.height());
|
||||
} else {
|
||||
const auto size = st::filterLinkPreview * sizeProgress;
|
||||
return QRectF(
|
||||
(width() - size) / 2.,
|
||||
st::filterLinkPreviewTop * topProgress,
|
||||
size,
|
||||
size);
|
||||
}
|
||||
};
|
||||
|
||||
void Widget::paintEvent(QPaintEvent *e) {
|
||||
@@ -298,10 +356,13 @@ void Widget::paintEvent(QPaintEvent *e) {
|
||||
if (_progress.top) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
if (_preview.isNull()) {
|
||||
_preview = GeneratePreview(
|
||||
_folderTitle,
|
||||
_folderIcon,
|
||||
_badge.current());
|
||||
const auto badge = _badge.current();
|
||||
if (_horizontalFilters) {
|
||||
_preview = GeneratePreview(this, _folderTitle, badge);
|
||||
Widget::resizeEvent(nullptr);
|
||||
} else {
|
||||
_preview = GeneratePreview(_folderTitle, _folderIcon, badge);
|
||||
}
|
||||
}
|
||||
p.drawImage(_previewRect, _preview);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ struct FilterLinkHeaderDescriptor {
|
||||
base::required<QString> folderTitle;
|
||||
not_null<const style::icon*> folderIcon;
|
||||
rpl::producer<int> badge;
|
||||
bool horizontalFilters = false;
|
||||
};
|
||||
|
||||
struct FilterLinkHeader {
|
||||
|
||||
@@ -107,9 +107,15 @@ private:
|
||||
std::array<int64, kMaxStreams> lastDts = { 0 };
|
||||
};
|
||||
|
||||
#if DA_FFMPEG_CONST_WRITE_CALLBACK
|
||||
static int Write(void *opaque, const uint8_t *_buf, int buf_size) {
|
||||
uint8_t *buf = const_cast<uint8_t *>(_buf);
|
||||
#else
|
||||
static int Write(void *opaque, uint8_t *buf, int buf_size) {
|
||||
#endif
|
||||
return static_cast<Private*>(opaque)->write(buf, buf_size);
|
||||
}
|
||||
|
||||
static int64_t Seek(void *opaque, int64_t offset, int whence) {
|
||||
return static_cast<Private*>(opaque)->seek(offset, whence);
|
||||
}
|
||||
@@ -388,7 +394,6 @@ bool RoundVideoRecorder::Private::initAudio() {
|
||||
_audioCodec->sample_rate = kAudioFrequency;
|
||||
#if DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||
_audioCodec->ch_layout = AV_CHANNEL_LAYOUT_MONO;
|
||||
_audioCodec->channels = _audioCodec->ch_layout.nb_channels;
|
||||
#else
|
||||
_audioCodec->channel_layout = AV_CH_LAYOUT_MONO;
|
||||
_audioCodec->channels = _audioChannels;
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/chat/group_call_userpics.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
@@ -8,8 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/text/text_block.h"
|
||||
#include "ui/widgets/menu/menu_item_base.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
|
||||
418
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp
Normal file
418
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp
Normal file
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
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 "ui/widgets/chat_filters_tabs_slider.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/widgets/side_bar_button.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
|
||||
namespace Ui {
|
||||
|
||||
ChatsFiltersTabs::ChatsFiltersTabs(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::SettingsSlider &st)
|
||||
: Ui::SettingsSlider(parent, st)
|
||||
, _st(st)
|
||||
, _unreadSt([&] {
|
||||
auto st = Ui::UnreadBadgeStyle();
|
||||
st.align = style::al_left;
|
||||
return st;
|
||||
}())
|
||||
, _unreadMaxString(u"99+"_q)
|
||||
, _unreadSkip(st::lineWidth * 5) {
|
||||
Expects(_st.barSnapToLabel && _st.strictSkip);
|
||||
if (_st.barRadius > 0) {
|
||||
_bar.emplace(_st.barRadius, _st.barFg);
|
||||
_barActive.emplace(_st.barRadius, _st.barFgActive);
|
||||
}
|
||||
{
|
||||
const auto one = Ui::CountUnreadBadgeSize(u"9"_q, _unreadSt, 1);
|
||||
_cachedBadgeWidths = {
|
||||
one.width(),
|
||||
Ui::CountUnreadBadgeSize(u"99"_q, _unreadSt, 2).width(),
|
||||
Ui::CountUnreadBadgeSize(u"999"_q, _unreadSt, 2).width(),
|
||||
};
|
||||
_cachedBadgeHeight = one.height();
|
||||
}
|
||||
Ui::DiscreteSlider::setSelectOnPress(false);
|
||||
}
|
||||
|
||||
int ChatsFiltersTabs::centerOfSection(int section) const {
|
||||
const auto widths = countSectionsWidths(0);
|
||||
auto result = 0;
|
||||
if (section >= 0 && section < widths.size()) {
|
||||
for (auto i = 0; i < section; i++) {
|
||||
result += widths[i];
|
||||
}
|
||||
result += widths[section] / 2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::fitWidthToSections() {
|
||||
const auto widths = countSectionsWidths(0);
|
||||
resizeToWidth(ranges::accumulate(widths, .0));
|
||||
_lockedFromX = calculateLockedFromX();
|
||||
|
||||
{
|
||||
_sections.clear();
|
||||
enumerateSections([&](Section §ion) {
|
||||
_sections.push_back({ not_null{ §ion }, 0, false });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::setUnreadCount(int index, int unreadCount) {
|
||||
const auto it = _unreadCounts.find(index);
|
||||
if (it == _unreadCounts.end()) {
|
||||
if (unreadCount) {
|
||||
_unreadCounts.emplace(index, Unread{
|
||||
.cache = cacheUnreadCount(unreadCount),
|
||||
.count = unreadCount,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (unreadCount) {
|
||||
it->second.count = unreadCount;
|
||||
it->second.cache = cacheUnreadCount(unreadCount);
|
||||
} else {
|
||||
_unreadCounts.erase(it);
|
||||
}
|
||||
}
|
||||
if (unreadCount) {
|
||||
const auto widthIndex = (unreadCount < 10)
|
||||
? 0
|
||||
: (unreadCount < 100)
|
||||
? 1
|
||||
: 2;
|
||||
setAdditionalContentWidthToSection(
|
||||
index,
|
||||
_cachedBadgeWidths[widthIndex] + _unreadSkip);
|
||||
} else {
|
||||
setAdditionalContentWidthToSection(index, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int ChatsFiltersTabs::calculateLockedFromX() const {
|
||||
if (!_lockedFrom) {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
auto left = 0;
|
||||
auto index = 0;
|
||||
enumerateSections([&](const Section §ion) {
|
||||
const auto currentRight = section.left + section.width;
|
||||
if (index == _lockedFrom) {
|
||||
return false;
|
||||
}
|
||||
left = currentRight;
|
||||
index++;
|
||||
return true;
|
||||
});
|
||||
return left ? left : std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::setLockedFrom(int index) {
|
||||
_lockedFrom = index;
|
||||
_lockedFromX = calculateLockedFromX();
|
||||
if (!index) {
|
||||
_paletteLifetime.destroy();
|
||||
return;
|
||||
}
|
||||
_paletteLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([this] {
|
||||
_lockCache.emplace(Ui::SideBarLockIcon(_st.labelFg));
|
||||
});
|
||||
}
|
||||
|
||||
QImage ChatsFiltersTabs::cacheUnreadCount(int count) const {
|
||||
const auto widthIndex = (count < 10) ? 0 : (count < 100) ? 1 : 2;
|
||||
auto image = QImage(
|
||||
QSize(_cachedBadgeWidths[widthIndex], _cachedBadgeHeight)
|
||||
* style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
const auto string = (count > 99)
|
||||
? _unreadMaxString
|
||||
: QString::number(count);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
Ui::PaintUnreadBadge(p, string, 0, 0, _unreadSt, 0);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto clip = e->rect();
|
||||
const auto range = getCurrentActiveRange();
|
||||
const auto activeIndex = activeSection();
|
||||
|
||||
auto index = 0;
|
||||
auto raisedIndex = -1;
|
||||
auto activeHorizontalShift = 0;
|
||||
const auto drawSection = [&](Section §ion) {
|
||||
// const auto activeWidth = _st.barSnapToLabel
|
||||
// ? section.contentWidth
|
||||
// : section.width;
|
||||
|
||||
const auto horizontalShift = _sections[index].horizontalShift;
|
||||
const auto shiftedLeft = section.left + horizontalShift;
|
||||
if (_sections[index].raise) {
|
||||
raisedIndex = index;
|
||||
}
|
||||
if (index == activeIndex) {
|
||||
activeHorizontalShift = horizontalShift;
|
||||
}
|
||||
|
||||
// const auto activeLeft = shiftedLeft
|
||||
// + (section.width - activeWidth) / 2;
|
||||
// const auto active = 1.
|
||||
// - std::clamp(
|
||||
// std::abs(range.left - activeLeft) / float64(range.width),
|
||||
// 0.,
|
||||
// 1.);
|
||||
const auto active = (index == activeIndex) ? 1. : 0.;
|
||||
if (section.ripple) {
|
||||
const auto color = anim::color(
|
||||
_st.rippleBg,
|
||||
_st.rippleBgActive,
|
||||
active);
|
||||
section.ripple->paint(p, shiftedLeft, 0, width(), &color);
|
||||
if (section.ripple->empty()) {
|
||||
section.ripple.reset();
|
||||
}
|
||||
}
|
||||
const auto labelLeft = shiftedLeft
|
||||
+ (section.width - section.contentWidth) / 2;
|
||||
const auto rect = myrtlrect(
|
||||
labelLeft,
|
||||
_st.labelTop,
|
||||
section.contentWidth,
|
||||
_st.labelStyle.font->height);
|
||||
if (rect.intersects(clip)) {
|
||||
const auto locked = (_lockedFrom && (index >= _lockedFrom));
|
||||
if (locked) {
|
||||
constexpr auto kPremiumLockedOpacity = 0.6;
|
||||
p.setOpacity(kPremiumLockedOpacity);
|
||||
}
|
||||
p.setPen(anim::pen(_st.labelFg, _st.labelFgActive, active));
|
||||
section.label.draw(p, {
|
||||
.position = QPoint(labelLeft, _st.labelTop),
|
||||
.outerWidth = width(),
|
||||
.availableWidth = section.label.maxWidth(),
|
||||
});
|
||||
{
|
||||
const auto it = _unreadCounts.find(index);
|
||||
if (it != _unreadCounts.end()) {
|
||||
p.drawImage(
|
||||
labelLeft
|
||||
+ _unreadSkip
|
||||
+ section.label.maxWidth(),
|
||||
_st.labelTop,
|
||||
it->second.cache);
|
||||
}
|
||||
}
|
||||
if (locked) {
|
||||
if (!_lockCache) {
|
||||
_lockCache.emplace(Ui::SideBarLockIcon(_st.labelFg));
|
||||
}
|
||||
const auto size = _lockCache->size()
|
||||
/ style::DevicePixelRatio();
|
||||
p.drawImage(
|
||||
labelLeft + (section.label.maxWidth() - size.width()) / 2,
|
||||
height() - size.height() - st::lineWidth,
|
||||
*_lockCache);
|
||||
p.setOpacity(1.0);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
return true;
|
||||
};
|
||||
enumerateSections(drawSection);
|
||||
if (raisedIndex >= 0) {
|
||||
index = raisedIndex;
|
||||
drawSection(*_sections[raisedIndex].section);
|
||||
}
|
||||
if (_st.barSnapToLabel) {
|
||||
const auto drawRect = [&](QRect rect, bool active) {
|
||||
const auto &bar = active ? _barActive : _bar;
|
||||
if (bar) {
|
||||
bar->paint(p, rect);
|
||||
} else {
|
||||
p.fillRect(rect, active ? _st.barFgActive : _st.barFg);
|
||||
}
|
||||
};
|
||||
const auto add = _st.barStroke / 2;
|
||||
const auto from = std::max(range.left - add, 0);
|
||||
const auto till = std::min(range.left + range.width + add, width());
|
||||
if (from < till) {
|
||||
drawRect(
|
||||
myrtlrect(
|
||||
from,
|
||||
_st.barTop,
|
||||
till - from,
|
||||
_st.barStroke).translated(activeHorizontalShift, 0),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::mousePressEvent(QMouseEvent *e) {
|
||||
const auto mouseButton = e->button();
|
||||
if (mouseButton == Qt::MouseButton::LeftButton) {
|
||||
_lockedPressed = (e->pos().x() >= _lockedFromX);
|
||||
if (_lockedPressed) {
|
||||
Ui::RpWidget::mousePressEvent(e);
|
||||
} else {
|
||||
Ui::SettingsSlider::mousePressEvent(e);
|
||||
}
|
||||
} else {
|
||||
Ui::RpWidget::mousePressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_reordering) {
|
||||
Ui::RpWidget::mouseMoveEvent(e);
|
||||
} else {
|
||||
Ui::SettingsSlider::mouseMoveEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::mouseReleaseEvent(QMouseEvent *e) {
|
||||
const auto mouseButton = e->button();
|
||||
if (mouseButton == Qt::MouseButton::LeftButton) {
|
||||
if (base::take(_lockedPressed)) {
|
||||
_lockedPressed = false;
|
||||
_lockedClicked.fire({});
|
||||
} else {
|
||||
if (_reordering) {
|
||||
for (const auto §ion : _sections) {
|
||||
if (section.section->ripple) {
|
||||
section.section->ripple->lastStop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ui::SettingsSlider::mouseReleaseEvent(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ui::RpWidget::mouseReleaseEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const auto pos = e->pos();
|
||||
if (pos.x() >= _lockedFromX) {
|
||||
return;
|
||||
}
|
||||
auto left = 0;
|
||||
auto index = 0;
|
||||
enumerateSections([&](const Section §ion) {
|
||||
const auto currentRight = section.left + section.width;
|
||||
if (pos.x() > left && pos.x() < currentRight) {
|
||||
return false;
|
||||
}
|
||||
left = currentRight;
|
||||
index++;
|
||||
return true;
|
||||
});
|
||||
_contextMenuRequested.fire_copy(index);
|
||||
}
|
||||
|
||||
rpl::producer<int> ChatsFiltersTabs::contextMenuRequested() const {
|
||||
return _contextMenuRequested.events();
|
||||
}
|
||||
|
||||
rpl::producer<> ChatsFiltersTabs::lockedClicked() const {
|
||||
return _lockedClicked.events();
|
||||
}
|
||||
|
||||
int ChatsFiltersTabs::count() const {
|
||||
return _sections.size();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::setHorizontalShift(int index, int shift) {
|
||||
Expects(index >= 0 && index < _sections.size());
|
||||
|
||||
auto §ion = _sections[index];
|
||||
if (const auto delta = shift - section.horizontalShift) {
|
||||
section.horizontalShift = shift;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::setRaised(int index) {
|
||||
_sections[index].raise = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::reorderSections(int oldIndex, int newIndex) {
|
||||
Expects(oldIndex >= 0 && oldIndex < _sections.size());
|
||||
Expects(newIndex >= 0 && newIndex < _sections.size());
|
||||
// Expects(!_inResize);
|
||||
auto lefts = std::vector<int>();
|
||||
enumerateSections([&](Section §ion) {
|
||||
lefts.emplace_back(section.left);
|
||||
return true;
|
||||
});
|
||||
const auto wasActive = activeSection();
|
||||
|
||||
{
|
||||
auto unreadCounts = base::flat_map<Index, Unread>();
|
||||
for (auto &[index, unread] : _unreadCounts) {
|
||||
unreadCounts.emplace(
|
||||
base::reorder_index(index, oldIndex, newIndex),
|
||||
std::move(unread));
|
||||
}
|
||||
_unreadCounts = std::move(unreadCounts);
|
||||
}
|
||||
|
||||
base::reorder(sectionsRef(), oldIndex, newIndex);
|
||||
Ui::DiscreteSlider::setActiveSectionFast(
|
||||
base::reorder_index(wasActive, oldIndex, newIndex));
|
||||
Ui::DiscreteSlider::stopAnimation();
|
||||
|
||||
{
|
||||
_sections.clear();
|
||||
auto left = 0;
|
||||
enumerateSections([&](Section §ion) {
|
||||
_sections.push_back({ not_null{ §ion }, 0, false });
|
||||
section.left = left;
|
||||
left += section.width;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
not_null<Ui::DiscreteSlider::Section*> ChatsFiltersTabs::widgetAt(
|
||||
int index) const {
|
||||
Expects(index >= 0 && index < count());
|
||||
|
||||
return _sections[index].section;
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::setReordering(int value) {
|
||||
_reordering = value;
|
||||
}
|
||||
|
||||
int ChatsFiltersTabs::reordering() const {
|
||||
return _reordering;
|
||||
}
|
||||
|
||||
void ChatsFiltersTabs::stopAnimation() {
|
||||
Ui::DiscreteSlider::stopAnimation();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
95
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h
Normal file
95
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
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 "ui/unread_badge_paint.h"
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
|
||||
namespace style {
|
||||
struct SettingsSlider;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class SettingsSlider;
|
||||
|
||||
class ChatsFiltersTabsReorder;
|
||||
|
||||
class ChatsFiltersTabs final : public Ui::SettingsSlider {
|
||||
public:
|
||||
ChatsFiltersTabs(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::SettingsSlider &st);
|
||||
|
||||
[[nodiscard]] int centerOfSection(int section) const;
|
||||
void fitWidthToSections();
|
||||
void setUnreadCount(int index, int unreadCount);
|
||||
void setLockedFrom(int index);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> contextMenuRequested() const;
|
||||
[[nodiscard]] rpl::producer<> lockedClicked() const;
|
||||
|
||||
void setHorizontalShift(int index, int shift);
|
||||
void setRaised(int index);
|
||||
[[nodiscard]] int count() const;
|
||||
void reorderSections(int oldIndex, int newIndex);
|
||||
[[nodiscard]] not_null<DiscreteSlider::Section*> widgetAt(int i) const;
|
||||
void setReordering(int value);
|
||||
[[nodiscard]] int reordering() const;
|
||||
|
||||
void stopAnimation();
|
||||
|
||||
protected:
|
||||
struct ShiftedSection {
|
||||
not_null<Ui::DiscreteSlider::Section*> section;
|
||||
int horizontalShift = 0;
|
||||
bool raise = false;
|
||||
};
|
||||
friend class ChatsFiltersTabsReorder;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
std::vector<ShiftedSection> _sections;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QImage cacheUnreadCount(int count) const;
|
||||
[[nodiscard]] int calculateLockedFromX() const;
|
||||
|
||||
using Index = int;
|
||||
struct Unread final {
|
||||
QImage cache;
|
||||
int count = 0;
|
||||
};
|
||||
base::flat_map<Index, Unread> _unreadCounts;
|
||||
const style::SettingsSlider &_st;
|
||||
const UnreadBadgeStyle _unreadSt;
|
||||
const QString _unreadMaxString;
|
||||
const int _unreadSkip;
|
||||
std::vector<int> _cachedBadgeWidths;
|
||||
int _cachedBadgeHeight = 0;
|
||||
int _lockedFrom = 0;
|
||||
int _lockedFromX = 0;
|
||||
bool _lockedPressed = false;
|
||||
std::optional<Ui::RoundRect> _bar;
|
||||
std::optional<Ui::RoundRect> _barActive;
|
||||
std::optional<QImage> _lockCache;
|
||||
|
||||
int _reordering = 0;
|
||||
|
||||
rpl::lifetime _paletteLifetime;
|
||||
rpl::event_stream<int> _contextMenuRequested;
|
||||
rpl::event_stream<> _lockedClicked;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
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 "ui/widgets/chat_filters_tabs_slider_reorder.h"
|
||||
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "styles/style_basic.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
#include <QtGui/QtEvents>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kScrollFactor = 0.05;
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatsFiltersTabsReorder::ChatsFiltersTabsReorder(
|
||||
not_null<ChatsFiltersTabs*> layout,
|
||||
not_null<ScrollArea*> scroll)
|
||||
: _layout(layout)
|
||||
, _scroll(scroll)
|
||||
, _scrollAnimation([this] { updateScrollCallback(); }) {
|
||||
}
|
||||
|
||||
ChatsFiltersTabsReorder::ChatsFiltersTabsReorder(
|
||||
not_null<ChatsFiltersTabs*> layout)
|
||||
: _layout(layout) {
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::cancel() {
|
||||
if (_currentWidget) {
|
||||
cancelCurrent(indexOf(_currentWidget));
|
||||
}
|
||||
_lifetime.destroy();
|
||||
for (auto i = 0, count = _layout->count(); i != count; ++i) {
|
||||
_layout->setHorizontalShift(i, 0);
|
||||
}
|
||||
_entries.clear();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::start() {
|
||||
const auto count = _layout->count();
|
||||
if (count < 2) {
|
||||
return;
|
||||
}
|
||||
_layout->events()
|
||||
| rpl::start_with_next_done([this](not_null<QEvent*> e) {
|
||||
switch (e->type()) {
|
||||
case QEvent::MouseMove:
|
||||
mouseMove(static_cast<QMouseEvent*>(e.get())->globalPos());
|
||||
break;
|
||||
case QEvent::MouseButtonPress: {
|
||||
const auto m = static_cast<QMouseEvent*>(e.get());
|
||||
mousePress(m->button(), m->pos(), m->globalPos());
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseButtonRelease:
|
||||
mouseRelease(static_cast<QMouseEvent*>(e.get())->button());
|
||||
break;
|
||||
}
|
||||
}, [this] {
|
||||
cancel();
|
||||
}, _lifetime);
|
||||
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto widget = _layout->widgetAt(i);
|
||||
_entries.push_back({ widget });
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::addPinnedInterval(int from, int length) {
|
||||
_pinnedIntervals.push_back({ from, length });
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::clearPinnedIntervals() {
|
||||
_pinnedIntervals.clear();
|
||||
}
|
||||
|
||||
bool ChatsFiltersTabsReorder::Interval::isIn(int index) const {
|
||||
return (index >= from) && (index < (from + length));
|
||||
}
|
||||
|
||||
bool ChatsFiltersTabsReorder::isIndexPinned(int index) const {
|
||||
return ranges::any_of(_pinnedIntervals, [&](const Interval &i) {
|
||||
return i.isIn(index);
|
||||
});
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::checkForStart(QPoint position) {
|
||||
const auto shift = position.x() - _currentStart;
|
||||
const auto delta = QApplication::startDragDistance();
|
||||
if (std::abs(shift) <= delta) {
|
||||
return;
|
||||
}
|
||||
_currentState = State::Started;
|
||||
_currentStart += (shift > 0) ? delta : -delta;
|
||||
|
||||
const auto index = indexOf(_currentWidget);
|
||||
_layout->setRaised(index);
|
||||
_currentDesiredIndex = index;
|
||||
_updates.fire({ _currentWidget, index, index, _currentState });
|
||||
|
||||
updateOrder(index, position);
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::updateOrder(int index, QPoint position) {
|
||||
if (isIndexPinned(index)) {
|
||||
return;
|
||||
}
|
||||
const auto shift = position.x() - _currentStart;
|
||||
auto ¤t = _entries[index];
|
||||
current.shiftAnimation.stop();
|
||||
current.shift = current.finalShift = shift;
|
||||
_layout->setHorizontalShift(index, shift);
|
||||
|
||||
checkForScrollAnimation();
|
||||
|
||||
const auto count = _entries.size();
|
||||
const auto currentWidth = current.widget->width;
|
||||
const auto currentMiddle = current.widget->left
|
||||
+ shift
|
||||
+ currentWidth / 2;
|
||||
_currentDesiredIndex = index;
|
||||
if (shift > 0) {
|
||||
for (auto next = index + 1; next != count; ++next) {
|
||||
if (isIndexPinned(next)) {
|
||||
return;
|
||||
}
|
||||
const auto &e = _entries[next];
|
||||
if (currentMiddle < e.widget->left + e.widget->width / 2) {
|
||||
moveToShift(next, 0);
|
||||
} else {
|
||||
_currentDesiredIndex = next;
|
||||
moveToShift(next, -currentWidth);
|
||||
}
|
||||
}
|
||||
for (auto prev = index - 1; prev >= 0; --prev) {
|
||||
moveToShift(prev, 0);
|
||||
}
|
||||
} else {
|
||||
for (auto next = index + 1; next != count; ++next) {
|
||||
moveToShift(next, 0);
|
||||
}
|
||||
for (auto prev = index - 1; prev >= 0; --prev) {
|
||||
if (isIndexPinned(prev)) {
|
||||
return;
|
||||
}
|
||||
const auto &e = _entries[prev];
|
||||
if (currentMiddle >= e.widget->left + e.widget->width / 2) {
|
||||
moveToShift(prev, 0);
|
||||
} else {
|
||||
_currentDesiredIndex = prev;
|
||||
moveToShift(prev, currentWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::mousePress(
|
||||
Qt::MouseButton button,
|
||||
QPoint position,
|
||||
QPoint globalPosition) {
|
||||
if (button != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
auto widget = (ChatsFiltersTabs::ShiftedSection*)(nullptr);
|
||||
for (auto i = 0; i != _layout->_sections.size(); ++i) {
|
||||
auto §ion = _layout->_sections[i];
|
||||
if ((position.x() >= section.section->left)
|
||||
&& (position.x() < (section.section->left + section.section->width))) {
|
||||
widget = §ion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cancelCurrent();
|
||||
_currentWidget = widget->section;
|
||||
_currentShiftedWidget = widget;
|
||||
_currentStart = globalPosition.x();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::mouseMove(QPoint position) {
|
||||
if (!_currentWidget) {
|
||||
// if (_currentWidget != widget) {
|
||||
return;
|
||||
} else if (_currentState != State::Started) {
|
||||
checkForStart(position);
|
||||
} else {
|
||||
updateOrder(indexOf(_currentWidget), position);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::mouseRelease(Qt::MouseButton button) {
|
||||
if (button != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
finishReordering();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::cancelCurrent() {
|
||||
if (_currentWidget) {
|
||||
cancelCurrent(indexOf(_currentWidget));
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::cancelCurrent(int index) {
|
||||
Expects(_currentWidget != nullptr);
|
||||
|
||||
if (_currentState == State::Started) {
|
||||
_currentState = State::Cancelled;
|
||||
_updates.fire({ _currentWidget, index, index, _currentState });
|
||||
}
|
||||
_currentWidget = nullptr;
|
||||
_currentShiftedWidget = nullptr;
|
||||
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||
moveToShift(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::finishReordering() {
|
||||
if (_scroll) {
|
||||
_scrollAnimation.stop();
|
||||
}
|
||||
finishCurrent();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::finishCurrent() {
|
||||
if (!_currentWidget) {
|
||||
return;
|
||||
}
|
||||
const auto index = indexOf(_currentWidget);
|
||||
if (_currentDesiredIndex == index || _currentState != State::Started) {
|
||||
cancelCurrent(index);
|
||||
return;
|
||||
}
|
||||
const auto result = _currentDesiredIndex;
|
||||
const auto widget = _currentWidget;
|
||||
_currentState = State::Cancelled;
|
||||
_currentWidget = nullptr;
|
||||
_currentShiftedWidget = nullptr;
|
||||
|
||||
auto ¤t = _entries[index];
|
||||
const auto width = current.widget->width;
|
||||
if (index < result) {
|
||||
auto sum = 0;
|
||||
for (auto i = index; i != result; ++i) {
|
||||
auto &entry = _entries[i + 1];
|
||||
const auto widget = entry.widget;
|
||||
entry.deltaShift += width;
|
||||
updateShift(widget, i + 1);
|
||||
sum += widget->width;
|
||||
}
|
||||
current.finalShift -= sum;
|
||||
} else if (index > result) {
|
||||
auto sum = 0;
|
||||
for (auto i = result; i != index; ++i) {
|
||||
auto &entry = _entries[i];
|
||||
const auto widget = entry.widget;
|
||||
entry.deltaShift -= width;
|
||||
updateShift(widget, i);
|
||||
sum += widget->width;
|
||||
}
|
||||
current.finalShift += sum;
|
||||
}
|
||||
if (!(current.finalShift + current.deltaShift)) {
|
||||
current.shift = 0;
|
||||
_layout->setHorizontalShift(index, 0);
|
||||
}
|
||||
base::reorder(_entries, index, result);
|
||||
_layout->reorderSections(index, _currentDesiredIndex);
|
||||
for (auto i = 0; i != _layout->sectionsRef().size(); ++i) {
|
||||
_entries[i].widget = &_layout->sectionsRef()[i];
|
||||
moveToShift(i, 0);
|
||||
}
|
||||
|
||||
_updates.fire({ widget, index, result, State::Applied });
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::moveToShift(int index, int shift) {
|
||||
auto &entry = _entries[index];
|
||||
if (entry.finalShift + entry.deltaShift == shift) {
|
||||
return;
|
||||
}
|
||||
const auto widget = entry.widget;
|
||||
entry.shiftAnimation.start(
|
||||
[=, this] { updateShift(widget, index); },
|
||||
entry.finalShift,
|
||||
shift - entry.deltaShift,
|
||||
st::slideWrapDuration);
|
||||
entry.finalShift = shift - entry.deltaShift;
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::updateShift(
|
||||
not_null<Section*> widget,
|
||||
int indexHint) {
|
||||
Expects(indexHint >= 0 && indexHint < _entries.size());
|
||||
|
||||
const auto index = (_entries[indexHint].widget == widget)
|
||||
? indexHint
|
||||
: indexOf(widget);
|
||||
auto &entry = _entries[index];
|
||||
entry.shift = base::SafeRound(
|
||||
entry.shiftAnimation.value(entry.finalShift)
|
||||
) + entry.deltaShift;
|
||||
if (entry.deltaShift && !entry.shiftAnimation.animating()) {
|
||||
entry.finalShift += entry.deltaShift;
|
||||
entry.deltaShift = 0;
|
||||
}
|
||||
_layout->setHorizontalShift(index, entry.shift);
|
||||
}
|
||||
|
||||
int ChatsFiltersTabsReorder::indexOf(not_null<Section*> widget) const {
|
||||
const auto i = ranges::find(_entries, widget, &Entry::widget);
|
||||
Assert(i != end(_entries));
|
||||
return i - begin(_entries);
|
||||
}
|
||||
|
||||
auto ChatsFiltersTabsReorder::updates() const -> rpl::producer<Single> {
|
||||
return _updates.events();
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::updateScrollCallback() {
|
||||
if (!_scroll) {
|
||||
return;
|
||||
}
|
||||
const auto delta = deltaFromEdge();
|
||||
const auto oldLeft = _scroll->scrollLeft();
|
||||
_scroll->horizontalScrollBar()->setValue(oldLeft + delta);
|
||||
const auto newLeft = _scroll->scrollLeft();
|
||||
|
||||
_currentStart += oldLeft - newLeft;
|
||||
if (newLeft == 0 || newLeft == _scroll->scrollLeftMax()) {
|
||||
_scrollAnimation.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsFiltersTabsReorder::checkForScrollAnimation() {
|
||||
if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
|
||||
return;
|
||||
}
|
||||
_scrollAnimation.start();
|
||||
}
|
||||
|
||||
int ChatsFiltersTabsReorder::deltaFromEdge() {
|
||||
Expects(_currentWidget != nullptr);
|
||||
Expects(_currentShiftedWidget != nullptr);
|
||||
Expects(_scroll);
|
||||
|
||||
const auto globalPosition = _layout->mapToGlobal(
|
||||
QPoint(
|
||||
_currentWidget->left + _currentShiftedWidget->horizontalShift,
|
||||
0));
|
||||
const auto localLeft = _scroll->mapFromGlobal(globalPosition).x();
|
||||
const auto localRight = localLeft
|
||||
+ _currentWidget->width
|
||||
- _scroll->width();
|
||||
|
||||
const auto isLeftEdge = (localLeft < 0);
|
||||
const auto isRightEdge = (localRight > 0);
|
||||
if (!isLeftEdge && !isRightEdge) {
|
||||
_scrollAnimation.stop();
|
||||
return 0;
|
||||
}
|
||||
return int((isRightEdge ? localRight : localLeft) * kScrollFactor);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 "ui/effects/animations.h"
|
||||
#include "ui/widgets/chat_filters_tabs_slider.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class ScrollArea;
|
||||
|
||||
class ChatsFiltersTabsReorder final {
|
||||
public:
|
||||
using Section = ChatsFiltersTabs::Section;
|
||||
enum class State : uchar {
|
||||
Started,
|
||||
Applied,
|
||||
Cancelled,
|
||||
};
|
||||
|
||||
struct Single {
|
||||
not_null<Section*> widget;
|
||||
int oldPosition = 0;
|
||||
int newPosition = 0;
|
||||
State state = State::Started;
|
||||
};
|
||||
|
||||
ChatsFiltersTabsReorder(
|
||||
not_null<ChatsFiltersTabs*> layout,
|
||||
not_null<ScrollArea*> scroll);
|
||||
ChatsFiltersTabsReorder(not_null<ChatsFiltersTabs*> layout);
|
||||
|
||||
void start();
|
||||
void cancel();
|
||||
void finishReordering();
|
||||
void addPinnedInterval(int from, int length);
|
||||
void clearPinnedIntervals();
|
||||
[[nodiscard]] rpl::producer<Single> updates() const;
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
not_null<Section*> widget;
|
||||
Ui::Animations::Simple shiftAnimation;
|
||||
int shift = 0;
|
||||
int finalShift = 0;
|
||||
int deltaShift = 0;
|
||||
};
|
||||
struct Interval {
|
||||
[[nodiscard]] bool isIn(int index) const;
|
||||
|
||||
int from = 0;
|
||||
int length = 0;
|
||||
};
|
||||
|
||||
void mousePress(Qt::MouseButton button, QPoint position, QPoint global);
|
||||
void mouseMove(QPoint position);
|
||||
void mouseRelease(Qt::MouseButton button);
|
||||
|
||||
void checkForStart(QPoint position);
|
||||
void updateOrder(int index, QPoint position);
|
||||
void cancelCurrent();
|
||||
void finishCurrent();
|
||||
void cancelCurrent(int index);
|
||||
|
||||
[[nodiscard]] int indexOf(not_null<Section*> widget) const;
|
||||
void moveToShift(int index, int shift);
|
||||
void updateShift(not_null<Section*> widget, int indexHint);
|
||||
|
||||
void updateScrollCallback();
|
||||
void checkForScrollAnimation();
|
||||
[[nodiscard]] int deltaFromEdge();
|
||||
|
||||
[[nodiscard]] bool isIndexPinned(int index) const;
|
||||
|
||||
const not_null<ChatsFiltersTabs*> _layout;
|
||||
Ui::ScrollArea *_scroll = nullptr;
|
||||
|
||||
Ui::Animations::Basic _scrollAnimation;
|
||||
|
||||
std::vector<Interval> _pinnedIntervals;
|
||||
|
||||
Section *_currentWidget = nullptr;
|
||||
ChatsFiltersTabs::ShiftedSection *_currentShiftedWidget = nullptr;
|
||||
int _currentStart = 0;
|
||||
int _currentDesiredIndex = 0;
|
||||
State _currentState = State::Cancelled;
|
||||
std::vector<Entry> _entries;
|
||||
rpl::event_stream<Single> _updates;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
433
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp
Normal file
433
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
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 "ui/widgets/chat_filters_tabs_strip.h"
|
||||
|
||||
#include "api/api_chat_filters_remove_manager.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_unread_value.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_folders.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/widgets/chat_filters_tabs_slider_reorder.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_dialogs.h" // dialogsSearchTabs
|
||||
#include "styles/style_media_player.h" // mediaPlayerMenuCheck
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
struct State final {
|
||||
Ui::Animations::Simple animation;
|
||||
std::optional<FilterId> lastFilterId = std::nullopt;
|
||||
rpl::lifetime unreadLifetime;
|
||||
base::unique_qptr<Ui::PopupMenu> menu;
|
||||
|
||||
Api::RemoveComplexChatFilter removeApi;
|
||||
bool waitingSuggested = false;
|
||||
|
||||
std::unique_ptr<Ui::ChatsFiltersTabsReorder> reorder;
|
||||
bool ignoreRefresh = false;
|
||||
};
|
||||
|
||||
void ShowMenu(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<State*> state,
|
||||
int index) {
|
||||
const auto session = &controller->session();
|
||||
|
||||
auto id = FilterId(0);
|
||||
{
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
if (index < 0 || index >= list.size()) {
|
||||
return;
|
||||
}
|
||||
id = list[index].id();
|
||||
}
|
||||
state->menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
const auto addAction = Ui::Menu::CreateAddActionCallback(
|
||||
state->menu.get());
|
||||
|
||||
if (id) {
|
||||
addAction(
|
||||
tr::lng_filters_context_edit(tr::now),
|
||||
[=] { EditExistingFilter(controller, id); },
|
||||
&st::menuIconEdit);
|
||||
|
||||
Window::MenuAddMarkAsReadChatListAction(
|
||||
controller,
|
||||
[=] { return session->data().chatsFilters().chatsList(id); },
|
||||
addAction);
|
||||
|
||||
auto showRemoveBox = [=] {
|
||||
state->removeApi.request(Ui::MakeWeak(parent), controller, id);
|
||||
};
|
||||
addAction(
|
||||
tr::lng_filters_context_remove(tr::now),
|
||||
std::move(showRemoveBox),
|
||||
&st::menuIconDelete);
|
||||
} else {
|
||||
auto customUnreadState = [=] {
|
||||
return Data::MainListMapUnreadState(
|
||||
session,
|
||||
session->data().chatsList()->unreadState());
|
||||
};
|
||||
Window::MenuAddMarkAsReadChatListAction(
|
||||
controller,
|
||||
[=] { return session->data().chatsList(); },
|
||||
addAction,
|
||||
std::move(customUnreadState));
|
||||
|
||||
auto openFiltersSettings = [=] {
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
if (filters->suggestedLoaded()) {
|
||||
controller->showSettings(Settings::Folders::Id());
|
||||
} else if (!state->waitingSuggested) {
|
||||
state->waitingSuggested = true;
|
||||
filters->requestSuggested();
|
||||
filters->suggestedUpdated(
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
controller->showSettings(Settings::Folders::Id());
|
||||
}, parent->lifetime());
|
||||
}
|
||||
};
|
||||
addAction(
|
||||
tr::lng_filters_setup_menu(tr::now),
|
||||
std::move(openFiltersSettings),
|
||||
&st::menuIconEdit);
|
||||
}
|
||||
if (state->menu->empty()) {
|
||||
state->menu = nullptr;
|
||||
return;
|
||||
}
|
||||
state->menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void ShowFiltersListMenu(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<State*> state,
|
||||
int active,
|
||||
Fn<void(int)> changeActive) {
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
|
||||
state->menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
const auto reorderAll = session->user()->isPremium();
|
||||
const auto maxLimit = (reorderAll ? 1 : 0)
|
||||
+ Data::PremiumLimits(session).dialogFiltersCurrent();
|
||||
const auto premiumFrom = (reorderAll ? 0 : 1) + maxLimit;
|
||||
|
||||
for (auto i = 0; i < list.size(); ++i) {
|
||||
const auto &filter = list[i];
|
||||
auto text = filter.title().isEmpty()
|
||||
? tr::lng_filters_all(tr::now)
|
||||
: filter.title();
|
||||
|
||||
const auto action = state->menu->addAction(std::move(text), [=] {
|
||||
if (i != active) {
|
||||
changeActive(i);
|
||||
}
|
||||
}, (i == active) ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
action->setEnabled(i < premiumFrom);
|
||||
}
|
||||
session->data().chatsFilters().changed() | rpl::start_with_next([=] {
|
||||
state->menu->hideMenu();
|
||||
}, state->menu->lifetime());
|
||||
|
||||
if (state->menu->empty()) {
|
||||
state->menu = nullptr;
|
||||
return;
|
||||
}
|
||||
state->menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Main::Session*> session,
|
||||
Fn<void(FilterId)> choose,
|
||||
bool trackActiveFilterAndUnreadAndReorder) {
|
||||
const auto window = Core::App().findWindow(parent);
|
||||
const auto controller = window ? window->sessionController() : nullptr;
|
||||
|
||||
const auto &scrollSt = st::defaultScrollArea;
|
||||
const auto wrap = Ui::CreateChild<Ui::SlideWrap<Ui::RpWidget>>(
|
||||
parent,
|
||||
object_ptr<Ui::RpWidget>(parent));
|
||||
if (!controller) {
|
||||
return wrap;
|
||||
}
|
||||
const auto container = wrap->entity();
|
||||
const auto scroll = Ui::CreateChild<Ui::ScrollArea>(container, scrollSt);
|
||||
const auto sliderPadding = st::dialogsSearchTabsPadding;
|
||||
const auto slider = scroll->setOwnedWidget(
|
||||
object_ptr<Ui::PaddingWrap<Ui::ChatsFiltersTabs>>(
|
||||
parent,
|
||||
object_ptr<Ui::ChatsFiltersTabs>(parent, st::dialogsSearchTabs),
|
||||
QMargins(sliderPadding, 0, sliderPadding, 0)))->entity();
|
||||
const auto state = wrap->lifetime().make_state<State>();
|
||||
if (trackActiveFilterAndUnreadAndReorder) {
|
||||
using Reorder = Ui::ChatsFiltersTabsReorder;
|
||||
state->reorder = std::make_unique<Reorder>(slider, scroll);
|
||||
const auto applyReorder = [=](
|
||||
int oldPosition,
|
||||
int newPosition) {
|
||||
if (newPosition == oldPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
const auto &list = filters->list();
|
||||
if (!session->user()->isPremium()) {
|
||||
if (list[0].id() != FilterId()) {
|
||||
filters->moveAllToFront();
|
||||
}
|
||||
}
|
||||
Assert(oldPosition >= 0 && oldPosition < list.size());
|
||||
Assert(newPosition >= 0 && newPosition < list.size());
|
||||
|
||||
auto order = ranges::views::all(
|
||||
list
|
||||
) | ranges::views::transform(
|
||||
&Data::ChatFilter::id
|
||||
) | ranges::to_vector;
|
||||
base::reorder(order, oldPosition, newPosition);
|
||||
|
||||
state->ignoreRefresh = true;
|
||||
filters->saveOrder(order);
|
||||
state->ignoreRefresh = false;
|
||||
};
|
||||
|
||||
state->reorder->updates(
|
||||
) | rpl::start_with_next([=](const Reorder::Single &data) {
|
||||
if (data.state == Reorder::State::Started) {
|
||||
slider->setReordering(slider->reordering() + 1);
|
||||
} else {
|
||||
Ui::PostponeCall(slider, [=] {
|
||||
slider->setReordering(slider->reordering() - 1);
|
||||
});
|
||||
if (data.state == Reorder::State::Applied) {
|
||||
applyReorder(data.oldPosition, data.newPosition);
|
||||
}
|
||||
}
|
||||
}, slider->lifetime());
|
||||
}
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
scroll->setCustomWheelProcess([=](not_null<QWheelEvent*> e) {
|
||||
const auto pixelDelta = e->pixelDelta();
|
||||
const auto angleDelta = e->angleDelta();
|
||||
if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) {
|
||||
return false;
|
||||
}
|
||||
const auto bar = scroll->horizontalScrollBar();
|
||||
const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y();
|
||||
bar->setValue(bar->value() - y);
|
||||
return true;
|
||||
});
|
||||
|
||||
const auto scrollToIndex = [=](int index, anim::type type) {
|
||||
const auto to = index
|
||||
? (slider->centerOfSection(index) - scroll->width() / 2)
|
||||
: 0;
|
||||
const auto bar = scroll->horizontalScrollBar();
|
||||
state->animation.stop();
|
||||
if (type == anim::type::instant) {
|
||||
bar->setValue(to);
|
||||
} else {
|
||||
state->animation.start(
|
||||
[=](float64 v) { bar->setValue(v); },
|
||||
bar->value(),
|
||||
std::min(to, bar->maximum()),
|
||||
st::defaultTabsSlider.duration);
|
||||
}
|
||||
};
|
||||
|
||||
const auto applyFilter = [=](const Data::ChatFilter &filter) {
|
||||
if (slider->reordering()) {
|
||||
return;
|
||||
}
|
||||
choose(filter.id());
|
||||
};
|
||||
|
||||
const auto filterByIndex = [=](int index) -> const Data::ChatFilter& {
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
Assert(index >= 0 && index < list.size());
|
||||
return list[index];
|
||||
};
|
||||
|
||||
const auto rebuild = [=] {
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
if ((list.size() <= 1) || state->ignoreRefresh) {
|
||||
return;
|
||||
}
|
||||
auto sections = ranges::views::all(
|
||||
list
|
||||
) | ranges::views::transform([](const Data::ChatFilter &filter) {
|
||||
return filter.title().isEmpty()
|
||||
? tr::lng_filters_all(tr::now)
|
||||
: filter.title();
|
||||
}) | ranges::to_vector;
|
||||
slider->setSections(std::move(sections));
|
||||
slider->fitWidthToSections();
|
||||
{
|
||||
const auto reorderAll = session->user()->isPremium();
|
||||
const auto maxLimit = (reorderAll ? 1 : 0)
|
||||
+ Data::PremiumLimits(session).dialogFiltersCurrent();
|
||||
const auto premiumFrom = (reorderAll ? 0 : 1) + maxLimit;
|
||||
slider->setLockedFrom((premiumFrom >= list.size())
|
||||
? 0
|
||||
: premiumFrom);
|
||||
slider->lockedClicked() | rpl::start_with_next([=] {
|
||||
controller->show(Box(FiltersLimitBox, session, std::nullopt));
|
||||
}, slider->lifetime());
|
||||
if (state->reorder) {
|
||||
state->reorder->cancel();
|
||||
if (!reorderAll) {
|
||||
state->reorder->addPinnedInterval(0, 1);
|
||||
}
|
||||
state->reorder->addPinnedInterval(
|
||||
premiumFrom,
|
||||
std::max(1, int(list.size()) - maxLimit));
|
||||
}
|
||||
}
|
||||
if (trackActiveFilterAndUnreadAndReorder) {
|
||||
auto includeMuted = Data::IncludeMutedCounterFoldersValue();
|
||||
state->unreadLifetime.destroy();
|
||||
for (auto i = 0; i < list.size(); i++) {
|
||||
rpl::combine(
|
||||
Data::UnreadStateValue(session, list[i].id()),
|
||||
rpl::duplicate(includeMuted)
|
||||
) | rpl::start_with_next([=](
|
||||
const Dialogs::UnreadState &state,
|
||||
bool includeMuted) {
|
||||
const auto muted = (state.chatsMuted + state.marksMuted);
|
||||
const auto count = (state.chats + state.marks)
|
||||
- (includeMuted ? 0 : muted);
|
||||
slider->setUnreadCount(i, count);
|
||||
slider->fitWidthToSections();
|
||||
}, state->unreadLifetime);
|
||||
}
|
||||
}
|
||||
[&] {
|
||||
const auto lookingId = state->lastFilterId.value_or(list[0].id());
|
||||
for (auto i = 0; i < list.size(); i++) {
|
||||
const auto &filter = list[i];
|
||||
if (filter.id() == lookingId) {
|
||||
const auto wasLast = !!state->lastFilterId;
|
||||
state->lastFilterId = filter.id();
|
||||
slider->setActiveSectionFast(i);
|
||||
scrollToIndex(
|
||||
i,
|
||||
wasLast ? anim::type::normal : anim::type::instant);
|
||||
applyFilter(filter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (list.size()) {
|
||||
const auto index = 0;
|
||||
const auto &filter = filterByIndex(index);
|
||||
state->lastFilterId = filter.id();
|
||||
slider->setActiveSectionFast(index);
|
||||
scrollToIndex(index, anim::type::instant);
|
||||
applyFilter(filter);
|
||||
}
|
||||
}();
|
||||
if (trackActiveFilterAndUnreadAndReorder) {
|
||||
controller->activeChatsFilter(
|
||||
) | rpl::start_with_next([=](FilterId id) {
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
for (auto i = 0; i < list.size(); ++i) {
|
||||
if (list[i].id() == id) {
|
||||
slider->setActiveSection(i);
|
||||
scrollToIndex(i, anim::type::normal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
state->reorder->finishReordering();
|
||||
}, slider->lifetime());
|
||||
}
|
||||
slider->sectionActivated() | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
if (slider->reordering()) {
|
||||
return;
|
||||
}
|
||||
const auto &filter = filterByIndex(index);
|
||||
state->lastFilterId = filter.id();
|
||||
scrollToIndex(index, anim::type::normal);
|
||||
applyFilter(filter);
|
||||
}, wrap->lifetime());
|
||||
slider->contextMenuRequested() | rpl::start_with_next([=](int index) {
|
||||
if (trackActiveFilterAndUnreadAndReorder) {
|
||||
ShowMenu(wrap, controller, state, index);
|
||||
} else {
|
||||
ShowFiltersListMenu(
|
||||
wrap,
|
||||
session,
|
||||
state,
|
||||
slider->activeSection(),
|
||||
[=](int i) { slider->setActiveSection(i); });
|
||||
}
|
||||
}, slider->lifetime());
|
||||
wrap->toggle((list.size() > 1), anim::type::instant);
|
||||
|
||||
if (state->reorder) {
|
||||
state->reorder->start();
|
||||
}
|
||||
};
|
||||
session->data().chatsFilters().changed(
|
||||
) | rpl::start_with_next(rebuild, wrap->lifetime());
|
||||
rebuild();
|
||||
|
||||
session->data().chatsFilters().isChatlistChanged(
|
||||
) | rpl::start_with_next([=](FilterId id) {
|
||||
if (!id || !state->lastFilterId || (id != state->lastFilterId)) {
|
||||
return;
|
||||
}
|
||||
for (const auto &filter : session->data().chatsFilters().list()) {
|
||||
if (filter.id() == id) {
|
||||
applyFilter(filter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, wrap->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
parent->widthValue() | rpl::filter(rpl::mappers::_1 > 0),
|
||||
slider->heightValue() | rpl::filter(rpl::mappers::_1 > 0)
|
||||
) | rpl::start_with_next([=](int w, int h) {
|
||||
scroll->resize(w, h + scrollSt.deltax * 4);
|
||||
container->resize(w, h);
|
||||
wrap->resize(w, h);
|
||||
}, wrap->lifetime());
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
26
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h
Normal file
26
Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui {
|
||||
|
||||
not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Main::Session*> session,
|
||||
Fn<void(FilterId)> choose,
|
||||
bool trackActiveFilterAndUnreadAndReorder = false);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
@@ -57,10 +57,21 @@ void DiscreteSlider::finishAnimating() {
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::setAdditionalContentWidthToSection(int index, int w) {
|
||||
if (index >= 0 && index < _sections.size()) {
|
||||
auto §ion = _sections[index];
|
||||
section.contentWidth = section.label.maxWidth() + w;
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::setSelectOnPress(bool selectOnPress) {
|
||||
_selectOnPress = selectOnPress;
|
||||
}
|
||||
|
||||
std::vector<DiscreteSlider::Section> &DiscreteSlider::sectionsRef() {
|
||||
return _sections;
|
||||
}
|
||||
|
||||
void DiscreteSlider::addSection(const QString &label) {
|
||||
_sections.push_back(Section(label, getLabelStyle()));
|
||||
resizeToWidth(width());
|
||||
@@ -112,7 +123,7 @@ DiscreteSlider::Range DiscreteSlider::getFinalActiveRange() const {
|
||||
return { 0, 0 };
|
||||
}
|
||||
const auto width = _snapToLabel
|
||||
? std::min(raw->width, raw->label.maxWidth())
|
||||
? std::min(raw->width, raw->contentWidth)
|
||||
: raw->width;
|
||||
return { raw->left + ((raw->width - width) / 2), width };
|
||||
}
|
||||
@@ -125,8 +136,7 @@ DiscreteSlider::Range DiscreteSlider::getCurrentActiveRange() const {
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Lambda>
|
||||
void DiscreteSlider::enumerateSections(Lambda callback) {
|
||||
void DiscreteSlider::enumerateSections(Fn<bool(Section&)> callback) {
|
||||
for (auto §ion : _sections) {
|
||||
if (!callback(section)) {
|
||||
return;
|
||||
@@ -134,9 +144,9 @@ void DiscreteSlider::enumerateSections(Lambda callback) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Lambda>
|
||||
void DiscreteSlider::enumerateSections(Lambda callback) const {
|
||||
for (auto §ion : _sections) {
|
||||
void DiscreteSlider::enumerateSections(
|
||||
Fn<bool(const Section&)> callback) const {
|
||||
for (const auto §ion : _sections) {
|
||||
if (!callback(section)) {
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +154,7 @@ void DiscreteSlider::enumerateSections(Lambda callback) const {
|
||||
}
|
||||
|
||||
void DiscreteSlider::mousePressEvent(QMouseEvent *e) {
|
||||
auto index = getIndexFromPosition(e->pos());
|
||||
const auto index = getIndexFromPosition(e->pos());
|
||||
if (_selectOnPress) {
|
||||
setSelectedSection(index);
|
||||
}
|
||||
@@ -153,17 +163,21 @@ void DiscreteSlider::mousePressEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void DiscreteSlider::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_pressed < 0) return;
|
||||
if (_pressed < 0) {
|
||||
return;
|
||||
}
|
||||
if (_selectOnPress) {
|
||||
setSelectedSection(getIndexFromPosition(e->pos()));
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::mouseReleaseEvent(QMouseEvent *e) {
|
||||
auto pressed = std::exchange(_pressed, -1);
|
||||
if (pressed < 0) return;
|
||||
const auto pressed = std::exchange(_pressed, -1);
|
||||
if (pressed < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto index = getIndexFromPosition(e->pos());
|
||||
const auto index = getIndexFromPosition(e->pos());
|
||||
if (pressed < _sections.size()) {
|
||||
if (_sections[pressed].ripple) {
|
||||
_sections[pressed].ripple->lastStop();
|
||||
@@ -175,14 +189,16 @@ void DiscreteSlider::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void DiscreteSlider::setSelectedSection(int index) {
|
||||
if (index < 0 || index >= _sections.size()) return;
|
||||
if (index < 0 || index >= _sections.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selected != index) {
|
||||
const auto from = getFinalActiveRange();
|
||||
_selected = index;
|
||||
const auto to = getFinalActiveRange();
|
||||
const auto duration = getAnimationDuration();
|
||||
const auto updater = [=] { update(); };
|
||||
const auto updater = [this] { update(); };
|
||||
_a_left.start(updater, from.left, to.left, duration);
|
||||
_a_width.start(updater, from.width, to.width, duration);
|
||||
_callbackAfterMs = crl::now() + duration;
|
||||
@@ -190,8 +206,8 @@ void DiscreteSlider::setSelectedSection(int index) {
|
||||
}
|
||||
|
||||
int DiscreteSlider::getIndexFromPosition(QPoint pos) {
|
||||
int count = _sections.size();
|
||||
for (int i = 0; i != count; ++i) {
|
||||
const auto count = _sections.size();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
if (_sections[i].left + _sections[i].width > pos.x()) {
|
||||
return i;
|
||||
}
|
||||
@@ -202,7 +218,8 @@ int DiscreteSlider::getIndexFromPosition(QPoint pos) {
|
||||
DiscreteSlider::Section::Section(
|
||||
const QString &label,
|
||||
const style::TextStyle &st)
|
||||
: label(st, label) {
|
||||
: label(st, label)
|
||||
, contentWidth(Section::label.maxWidth()) {
|
||||
}
|
||||
|
||||
DiscreteSlider::Section::Section(
|
||||
@@ -210,6 +227,7 @@ DiscreteSlider::Section::Section(
|
||||
const style::TextStyle &st,
|
||||
const std::any &context) {
|
||||
this->label.setMarkedText(st, label, kMarkupTextOptions, context);
|
||||
contentWidth = Section::label.maxWidth();
|
||||
}
|
||||
|
||||
SettingsSlider::SettingsSlider(
|
||||
@@ -237,10 +255,12 @@ int SettingsSlider::getAnimationDuration() const {
|
||||
}
|
||||
|
||||
void SettingsSlider::resizeSections(int newWidth) {
|
||||
auto count = getSectionsCount();
|
||||
if (!count) return;
|
||||
const auto count = getSectionsCount();
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto sectionWidths = countSectionsWidths(newWidth);
|
||||
const auto sectionWidths = countSectionsWidths(newWidth);
|
||||
|
||||
auto skip = 0;
|
||||
auto x = 0.;
|
||||
@@ -258,18 +278,17 @@ void SettingsSlider::resizeSections(int newWidth) {
|
||||
stopAnimation();
|
||||
}
|
||||
|
||||
std::vector<float64> SettingsSlider::countSectionsWidths(
|
||||
int newWidth) const {
|
||||
auto count = getSectionsCount();
|
||||
auto sectionsWidth = newWidth - (count - 1) * _st.barSkip;
|
||||
auto sectionWidth = sectionsWidth / float64(count);
|
||||
std::vector<float64> SettingsSlider::countSectionsWidths(int newWidth) const {
|
||||
const auto count = getSectionsCount();
|
||||
const auto sectionsWidth = newWidth - (count - 1) * _st.barSkip;
|
||||
const auto sectionWidth = sectionsWidth / float64(count);
|
||||
|
||||
auto result = std::vector<float64>(count, sectionWidth);
|
||||
auto labelsWidth = 0;
|
||||
auto commonWidth = true;
|
||||
enumerateSections([&](const Section §ion) {
|
||||
labelsWidth += section.label.maxWidth();
|
||||
if (section.label.maxWidth() >= sectionWidth) {
|
||||
labelsWidth += section.contentWidth;
|
||||
if (section.contentWidth >= sectionWidth) {
|
||||
commonWidth = false;
|
||||
}
|
||||
return true;
|
||||
@@ -283,7 +302,7 @@ std::vector<float64> SettingsSlider::countSectionsWidths(
|
||||
enumerateSections([&](const Section §ion) {
|
||||
Expects(currentWidth != result.end());
|
||||
|
||||
*currentWidth = padding + section.label.maxWidth() + padding;
|
||||
*currentWidth = padding + section.contentWidth + padding;
|
||||
++currentWidth;
|
||||
return true;
|
||||
});
|
||||
@@ -297,7 +316,9 @@ int SettingsSlider::resizeGetHeight(int newWidth) {
|
||||
}
|
||||
|
||||
void SettingsSlider::startRipple(int sectionIndex) {
|
||||
if (!_st.ripple.showDuration) return;
|
||||
if (!_st.ripple.showDuration) {
|
||||
return;
|
||||
}
|
||||
auto index = 0;
|
||||
enumerateSections([this, &index, sectionIndex](Section §ion) {
|
||||
if (index++ == sectionIndex) {
|
||||
@@ -319,13 +340,13 @@ void SettingsSlider::startRipple(int sectionIndex) {
|
||||
QImage SettingsSlider::prepareRippleMask(
|
||||
int sectionIndex,
|
||||
const Section §ion) {
|
||||
auto size = QSize(section.width, height() - _st.rippleBottomSkip);
|
||||
const auto size = QSize(section.width, height() - _st.rippleBottomSkip);
|
||||
if (!_rippleTopRoundRadius
|
||||
|| (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) {
|
||||
return RippleAnimation::RectMask(size);
|
||||
}
|
||||
return RippleAnimation::MaskByDrawer(size, false, [&](QPainter &p) {
|
||||
auto plusRadius = _rippleTopRoundRadius + 1;
|
||||
const auto plusRadius = _rippleTopRoundRadius + 1;
|
||||
p.drawRoundedRect(
|
||||
0,
|
||||
0,
|
||||
@@ -347,10 +368,10 @@ QImage SettingsSlider::prepareRippleMask(
|
||||
}
|
||||
|
||||
void SettingsSlider::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
auto p = QPainter(this);
|
||||
|
||||
auto clip = e->rect();
|
||||
auto range = getCurrentActiveRange();
|
||||
const auto clip = e->rect();
|
||||
const auto range = DiscreteSlider::getCurrentActiveRange();
|
||||
|
||||
const auto drawRect = [&](QRect rect, bool active = false) {
|
||||
const auto &bar = active ? _barActive : _bar;
|
||||
@@ -362,32 +383,39 @@ void SettingsSlider::paintEvent(QPaintEvent *e) {
|
||||
};
|
||||
enumerateSections([&](Section §ion) {
|
||||
const auto activeWidth = _st.barSnapToLabel
|
||||
? section.label.maxWidth()
|
||||
? section.contentWidth
|
||||
: section.width;
|
||||
const auto activeLeft = section.left
|
||||
+ (section.width - activeWidth) / 2;
|
||||
auto active = 1.
|
||||
const auto active = 1.
|
||||
- std::clamp(
|
||||
qAbs(range.left - activeLeft) / float64(range.width),
|
||||
std::abs(range.left - activeLeft) / float64(range.width),
|
||||
0.,
|
||||
1.);
|
||||
if (section.ripple) {
|
||||
auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
|
||||
const auto color = anim::color(
|
||||
_st.rippleBg,
|
||||
_st.rippleBgActive,
|
||||
active);
|
||||
section.ripple->paint(p, section.left, 0, width(), &color);
|
||||
if (section.ripple->empty()) {
|
||||
section.ripple.reset();
|
||||
}
|
||||
}
|
||||
if (!_st.barSnapToLabel) {
|
||||
auto from = activeLeft, tofill = activeWidth;
|
||||
auto from = activeLeft;
|
||||
auto tofill = activeWidth;
|
||||
if (range.left > from) {
|
||||
auto fill = qMin(tofill, range.left - from);
|
||||
const auto fill = std::min(tofill, range.left - from);
|
||||
drawRect(myrtlrect(from, _st.barTop, fill, _st.barStroke));
|
||||
from += fill;
|
||||
tofill -= fill;
|
||||
}
|
||||
if (range.left + activeWidth > from) {
|
||||
if (auto fill = qMin(tofill, range.left + activeWidth - from)) {
|
||||
const auto fill = std::min(
|
||||
tofill,
|
||||
range.left + activeWidth - from);
|
||||
if (fill) {
|
||||
drawRect(
|
||||
myrtlrect(from, _st.barTop, fill, _st.barStroke),
|
||||
true);
|
||||
@@ -399,15 +427,20 @@ void SettingsSlider::paintEvent(QPaintEvent *e) {
|
||||
drawRect(myrtlrect(from, _st.barTop, tofill, _st.barStroke));
|
||||
}
|
||||
}
|
||||
const auto labelLeft = section.left + (section.width - section.label.maxWidth()) / 2;
|
||||
if (myrtlrect(labelLeft, _st.labelTop, section.label.maxWidth(), _st.labelStyle.font->height).intersects(clip)) {
|
||||
const auto labelLeft = section.left
|
||||
+ (section.width - section.contentWidth) / 2;
|
||||
const auto rect = myrtlrect(
|
||||
labelLeft,
|
||||
_st.labelTop,
|
||||
section.contentWidth,
|
||||
_st.labelStyle.font->height);
|
||||
if (rect.intersects(clip)) {
|
||||
p.setPen(anim::pen(_st.labelFg, _st.labelFgActive, active));
|
||||
section.label.drawLeft(
|
||||
p,
|
||||
labelLeft,
|
||||
_st.labelTop,
|
||||
section.label.maxWidth(),
|
||||
width());
|
||||
section.label.draw(p, {
|
||||
.position = QPoint(labelLeft, _st.labelTop),
|
||||
.outerWidth = width(),
|
||||
.availableWidth = section.label.maxWidth(),
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -416,7 +449,9 @@ void SettingsSlider::paintEvent(QPaintEvent *e) {
|
||||
const auto from = std::max(range.left - add, 0);
|
||||
const auto till = std::min(range.left + range.width + add, width());
|
||||
if (from < till) {
|
||||
drawRect(myrtlrect(from, _st.barTop, till - from, _st.barStroke), true);
|
||||
drawRect(
|
||||
myrtlrect(from, _st.barTop, till - from, _st.barStroke),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace style {
|
||||
struct TextStyle;
|
||||
struct SettingsSlider;
|
||||
} // namespace style
|
||||
|
||||
namespace st {
|
||||
extern const style::SettingsSlider &defaultSettingsSlider;
|
||||
} // namespace st
|
||||
|
||||
namespace Ui {
|
||||
|
||||
@@ -36,6 +44,8 @@ public:
|
||||
void setActiveSectionFast(int index);
|
||||
void finishAnimating();
|
||||
|
||||
void setAdditionalContentWidthToSection(int index, int width);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> sectionActivated() const {
|
||||
return _sectionActivated.events();
|
||||
}
|
||||
@@ -55,10 +65,11 @@ protected:
|
||||
const style::TextStyle &st,
|
||||
const std::any &context);
|
||||
|
||||
int left = 0;
|
||||
int width = 0;
|
||||
Ui::Text::String label;
|
||||
std::unique_ptr<RippleAnimation> ripple;
|
||||
int left = 0;
|
||||
int width = 0;
|
||||
int contentWidth = 0;
|
||||
};
|
||||
struct Range {
|
||||
int left = 0;
|
||||
@@ -72,11 +83,8 @@ protected:
|
||||
return _sections.size();
|
||||
}
|
||||
|
||||
template <typename Lambda>
|
||||
void enumerateSections(Lambda callback);
|
||||
|
||||
template <typename Lambda>
|
||||
void enumerateSections(Lambda callback) const;
|
||||
void enumerateSections(Fn<bool(Section&)> callback);
|
||||
void enumerateSections(Fn<bool(const Section&)> callback) const;
|
||||
|
||||
virtual void startRipple(int sectionIndex) {
|
||||
}
|
||||
@@ -89,6 +97,8 @@ protected:
|
||||
|
||||
void setSelectOnPress(bool selectOnPress);
|
||||
|
||||
std::vector<Section> §ionsRef();
|
||||
|
||||
private:
|
||||
void activateCallback();
|
||||
virtual const style::TextStyle &getLabelStyle() const = 0;
|
||||
@@ -116,7 +126,9 @@ private:
|
||||
|
||||
class SettingsSlider : public DiscreteSlider {
|
||||
public:
|
||||
SettingsSlider(QWidget *parent, const style::SettingsSlider &st = st::defaultSettingsSlider);
|
||||
SettingsSlider(
|
||||
QWidget *parent,
|
||||
const style::SettingsSlider &st = st::defaultSettingsSlider);
|
||||
|
||||
void setRippleTopRoundRadius(int radius);
|
||||
|
||||
@@ -127,13 +139,14 @@ protected:
|
||||
|
||||
void startRipple(int sectionIndex) override;
|
||||
|
||||
std::vector<float64> countSectionsWidths(int newWidth) const;
|
||||
|
||||
private:
|
||||
const style::TextStyle &getLabelStyle() const override;
|
||||
int getAnimationDuration() const override;
|
||||
QImage prepareRippleMask(int sectionIndex, const Section §ion);
|
||||
|
||||
void resizeSections(int newWidth);
|
||||
std::vector<float64> countSectionsWidths(int newWidth) const;
|
||||
|
||||
const style::SettingsSlider &_st;
|
||||
std::optional<Ui::RoundRect> _bar;
|
||||
|
||||
@@ -229,7 +229,8 @@ void Controller::setupSideBar() {
|
||||
sideBarChanged();
|
||||
}, _sessionController->lifetime());
|
||||
|
||||
if (_sessionController->session().settings().dialogsFiltersEnabled()) {
|
||||
if (_sessionController->session().settings().dialogsFiltersEnabled()
|
||||
&& !Core::App().settings().chatFiltersHorizontal()) {
|
||||
_sessionController->toggleFiltersMenu(true);
|
||||
} else {
|
||||
sideBarChanged();
|
||||
|
||||
@@ -13,15 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_main_menu.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_unread_value.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
@@ -41,42 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace Window {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state) {
|
||||
const auto folderId = Data::Folder::kId;
|
||||
if (const auto folder = session->data().folderLoaded(folderId)) {
|
||||
return state - folder->chatsList()->unreadState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<Dialogs::UnreadState> MainListUnreadState(
|
||||
not_null<Dialogs::MainList*> list) {
|
||||
return rpl::single(rpl::empty) | rpl::then(
|
||||
list->unreadStateChanges() | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
return list->unreadState();
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId) {
|
||||
if (filterId > 0) {
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
return MainListUnreadState(filters->chatsList(filterId));
|
||||
}
|
||||
return MainListUnreadState(
|
||||
session->data().chatsList()
|
||||
) | rpl::map([=](const Dialogs::UnreadState &state) {
|
||||
return MainListMapUnreadState(session, state);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FiltersMenu::FiltersMenu(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
@@ -88,14 +49,7 @@ FiltersMenu::FiltersMenu(
|
||||
, _scroll(&_outer)
|
||||
, _container(
|
||||
_scroll.setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(&_scroll)))
|
||||
, _includeMuted(Core::App().settings().includeMutedCounterFolders()) {
|
||||
Core::App().notifications().settingsChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == Window::Notifications::ChangeType::IncludeMuted
|
||||
) | rpl::start_with_next([=] {
|
||||
_includeMuted = Core::App().settings().includeMutedCounterFolders();
|
||||
}, _outer.lifetime());
|
||||
object_ptr<Ui::VerticalLayout>(&_scroll))) {
|
||||
|
||||
_drag.timer.setCallback([=] {
|
||||
if (_drag.filterId >= 0) {
|
||||
@@ -266,7 +220,7 @@ void FiltersMenu::setupList() {
|
||||
_reorder = std::make_unique<Ui::VerticalLayoutReorder>(_list, &_scroll);
|
||||
|
||||
_reorder->updates(
|
||||
) | rpl::start_with_next([=](Ui::VerticalLayoutReorder::Single data) {
|
||||
) | rpl::start_with_next([=](Ui::VerticalLayoutReorder::Single data) {
|
||||
using State = Ui::VerticalLayoutReorder::State;
|
||||
if (data.state == State::Started) {
|
||||
++_reordering;
|
||||
@@ -310,8 +264,8 @@ base::unique_qptr<Ui::SideBarButton> FiltersMenu::prepareButton(
|
||||
raw->setIconOverride(icons.normal, icons.active);
|
||||
if (id >= 0) {
|
||||
rpl::combine(
|
||||
UnreadStateValue(&_session->session(), id),
|
||||
_includeMuted.value()
|
||||
Data::UnreadStateValue(&_session->session(), id),
|
||||
Data::IncludeMutedCounterFoldersValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const Dialogs::UnreadState &state,
|
||||
bool includeMuted) {
|
||||
@@ -413,7 +367,7 @@ void FiltersMenu::showMenu(QPoint position, FilterId id) {
|
||||
if (id) {
|
||||
addAction(
|
||||
tr::lng_filters_context_edit(tr::now),
|
||||
[=] { showEditBox(id); },
|
||||
[=] { EditExistingFilter(_session, id); },
|
||||
&st::menuIconEdit);
|
||||
|
||||
auto filteredChats = [=] {
|
||||
@@ -426,12 +380,12 @@ void FiltersMenu::showMenu(QPoint position, FilterId id) {
|
||||
|
||||
addAction(
|
||||
tr::lng_filters_context_remove(tr::now),
|
||||
[=] { showRemoveBox(id); },
|
||||
[=] { _removeApi.request(Ui::MakeWeak(&_outer), _session, id); },
|
||||
&st::menuIconDelete);
|
||||
} else {
|
||||
auto customUnreadState = [=] {
|
||||
const auto session = &_session->session();
|
||||
return MainListMapUnreadState(
|
||||
return Data::MainListMapUnreadState(
|
||||
session,
|
||||
session->data().chatsList()->unreadState());
|
||||
};
|
||||
@@ -453,105 +407,6 @@ void FiltersMenu::showMenu(QPoint position, FilterId id) {
|
||||
_popupMenu->popup(position);
|
||||
}
|
||||
|
||||
void FiltersMenu::showEditBox(FilterId id) {
|
||||
EditExistingFilter(_session, id);
|
||||
}
|
||||
|
||||
void FiltersMenu::showRemoveBox(FilterId id) {
|
||||
const auto session = &_session->session();
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
|
||||
const auto has = filter.hasMyLinks();
|
||||
const auto confirm = [=](Fn<void()> action, bool onlyWhenHas = false) {
|
||||
if (!has && onlyWhenHas) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
_session->window().show(Ui::MakeConfirmBox({
|
||||
.text = (has
|
||||
? tr::lng_filters_delete_sure()
|
||||
: tr::lng_filters_remove_sure()),
|
||||
.confirmed = [=](Fn<void()> &&close) { close(); action(); },
|
||||
.confirmText = (has
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_filters_remove_yes()),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
const auto simple = [=] {
|
||||
confirm([=] { remove(id); });
|
||||
};
|
||||
const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
|
||||
if (suggestRemoving.empty()) {
|
||||
simple();
|
||||
return;
|
||||
} else if (_removingRequestId) {
|
||||
if (_removingId == id) {
|
||||
return;
|
||||
}
|
||||
session->api().request(_removingRequestId).cancel();
|
||||
}
|
||||
_removingId = id;
|
||||
_removingRequestId = session->api().request(
|
||||
MTPchatlists_GetLeaveChatlistSuggestions(
|
||||
MTP_inputChatlistDialogFilter(
|
||||
MTP_int(id)))
|
||||
).done(crl::guard(&_outer, [=](const MTPVector<MTPPeer> &result) {
|
||||
_removingRequestId = 0;
|
||||
const auto suggestRemovePeers = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([=](const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto chosen = crl::guard(&_outer, [=](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
remove(id, std::move(peers));
|
||||
});
|
||||
confirm(crl::guard(&_outer, [=] {
|
||||
Api::ProcessFilterRemove(
|
||||
_session,
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
suggestRemoving,
|
||||
suggestRemovePeers,
|
||||
chosen);
|
||||
}), true);
|
||||
})).fail(crl::guard(&_outer, [=] {
|
||||
_removingRequestId = 0;
|
||||
simple();
|
||||
})).send();
|
||||
}
|
||||
|
||||
void FiltersMenu::remove(
|
||||
FilterId id,
|
||||
std::vector<not_null<PeerData*>> leave) {
|
||||
const auto session = &_session->session();
|
||||
const auto api = &session->api();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag(0)),
|
||||
MTP_int(id),
|
||||
MTPDialogFilter()));
|
||||
if (leave.empty()) {
|
||||
api->request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
|
||||
MTP_int(id),
|
||||
MTPDialogFilter()
|
||||
)).send();
|
||||
} else {
|
||||
api->request(MTPchatlists_LeaveChatlist(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(id)),
|
||||
MTP_vector<MTPInputPeer>(ranges::views::all(
|
||||
leave
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
}) | ranges::to<QVector<MTPInputPeer>>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
void FiltersMenu::applyReorder(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
int oldPosition,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "api/api_chat_filters_remove_manager.h"
|
||||
#include "base/timer.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/widgets/side_bar_button.h"
|
||||
@@ -48,9 +49,6 @@ private:
|
||||
bool toBeginning = false);
|
||||
void setupMainMenuIcon();
|
||||
void showMenu(QPoint position, FilterId id);
|
||||
void showEditBox(FilterId id);
|
||||
void showRemoveBox(FilterId id);
|
||||
void remove(FilterId id, std::vector<not_null<PeerData*>> leave = {});
|
||||
void scrollToButton(not_null<Ui::RpWidget*> widget);
|
||||
void openFiltersSettings();
|
||||
|
||||
@@ -70,6 +68,8 @@ private:
|
||||
bool _ignoreRefresh = false;
|
||||
bool _waitingSuggested = false;
|
||||
|
||||
Api::RemoveComplexChatFilter _removeApi;
|
||||
|
||||
FilterId _removingId = 0;
|
||||
mtpRequestId _removingRequestId = 0;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "window/window_peer_menu.h"
|
||||
|
||||
#include "api/api_report.h"
|
||||
#include "menu/menu_check_item.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
@@ -38,10 +37,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_contact_box.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "inline_bots/bot_attach_web_view.h" // InlineBots::PeerType.
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -2053,7 +2052,77 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||
const auto state = [&] {
|
||||
auto controller = std::make_unique<Controller>(session);
|
||||
const auto controllerRaw = controller.get();
|
||||
auto box = Box<ListBox>(std::move(controller), nullptr);
|
||||
auto init = [=](not_null<PeerListBox*> box) {
|
||||
box->setSpecialTabMode(true);
|
||||
auto applyFilter = [=](FilterId id) {
|
||||
box->scrollToY(0);
|
||||
auto &filters = session->data().chatsFilters();
|
||||
const auto &list = filters.list();
|
||||
if (list.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
const auto pinned = filters.chatsList(id)->pinned()->order();
|
||||
box->peerListSortRows([&](
|
||||
const PeerListRow &r1,
|
||||
const PeerListRow &r2) {
|
||||
const auto it1 = ranges::find_if(pinned, [&](
|
||||
const Dialogs::Key &k) {
|
||||
return k.peer() == r1.peer();
|
||||
});
|
||||
const auto it2 = ranges::find_if(pinned, [&](
|
||||
const Dialogs::Key &k) {
|
||||
return k.peer() == r2.peer();
|
||||
});
|
||||
if (it1 == pinned.end() && it2 != pinned.end()) {
|
||||
return false;
|
||||
} else if (it2 == pinned.end() && it1 != pinned.end()) {
|
||||
return true;
|
||||
} else if (it1 != pinned.end() && it2 != pinned.end()) {
|
||||
return it1 < it2;
|
||||
}
|
||||
const auto history1 = session->data().history(r1.peer());
|
||||
const auto history2 = session->data().history(r2.peer());
|
||||
const auto date1 = history1->lastMessage()
|
||||
? history1->lastMessage()->date()
|
||||
: TimeId(0);
|
||||
const auto date2 = history2->lastMessage()
|
||||
? history2->lastMessage()->date()
|
||||
: TimeId(0);
|
||||
return date1 > date2;
|
||||
});
|
||||
const auto filter = ranges::find(
|
||||
list,
|
||||
id,
|
||||
&Data::ChatFilter::id);
|
||||
if (filter == list.end()) {
|
||||
return;
|
||||
}
|
||||
box->peerListPartitionRows([&](const PeerListRow &row) {
|
||||
const auto rowPtr = const_cast<PeerListRow*>(&row);
|
||||
if (!filter->id()) {
|
||||
box->peerListSetRowHidden(rowPtr, false);
|
||||
} else {
|
||||
const auto result = filter->contains(
|
||||
session->data().history(row.peer()));
|
||||
box->peerListSetRowHidden(rowPtr, !result);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
box->peerListRefreshRows();
|
||||
};
|
||||
const auto chatsFilters = Ui::AddChatFiltersTabsStrip(
|
||||
box,
|
||||
session,
|
||||
std::move(applyFilter));
|
||||
chatsFilters->lower();
|
||||
chatsFilters->heightValue() | rpl::start_with_next([box](int h) {
|
||||
box->setAddedTopScrollSkip(h);
|
||||
}, box->lifetime());
|
||||
box->multiSelectHeightValue() | rpl::start_with_next([=](int h) {
|
||||
chatsFilters->moveToLeft(0, h);
|
||||
}, chatsFilters->lifetime());
|
||||
};
|
||||
auto box = Box<ListBox>(std::move(controller), std::move(init));
|
||||
const auto boxRaw = box.data();
|
||||
boxRaw->setForwardOptions({
|
||||
.sendersCount = sendersCount,
|
||||
|
||||
@@ -1314,11 +1314,21 @@ SessionController::SessionController(
|
||||
closeFolder();
|
||||
}, lifetime());
|
||||
|
||||
session->data().chatsFilters().changed(
|
||||
rpl::merge(
|
||||
Core::App().settings().chatFiltersHorizontalChanges() | rpl::to_empty,
|
||||
session->data().chatsFilters().changed()
|
||||
) | rpl::start_with_next([=] {
|
||||
checkOpenedFilter();
|
||||
crl::on_main(this, [=] {
|
||||
refreshFiltersMenu();
|
||||
crl::on_main(this, [this] {
|
||||
if (SessionNavigation::session().data().chatsFilters().has()) {
|
||||
const auto isHorizontal
|
||||
= Core::App().settings().chatFiltersHorizontal();
|
||||
content()->toggleFiltersMenu(isHorizontal);
|
||||
toggleFiltersMenu(!isHorizontal);
|
||||
} else {
|
||||
content()->toggleFiltersMenu(false);
|
||||
toggleFiltersMenu(false);
|
||||
}
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
@@ -1549,10 +1559,6 @@ void SessionController::toggleFiltersMenu(bool enabled) {
|
||||
_filtersMenuChanged.fire({});
|
||||
}
|
||||
|
||||
void SessionController::refreshFiltersMenu() {
|
||||
toggleFiltersMenu(session().data().chatsFilters().has());
|
||||
}
|
||||
|
||||
rpl::producer<> SessionController::filtersMenuChanged() const {
|
||||
return _filtersMenuChanged.events();
|
||||
}
|
||||
|
||||
@@ -630,7 +630,6 @@ private:
|
||||
|
||||
void init();
|
||||
void setupShortcuts();
|
||||
void refreshFiltersMenu();
|
||||
void checkOpenedFilter();
|
||||
void suggestArchiveAndMute();
|
||||
void activateFirstChatsFilter();
|
||||
|
||||
@@ -70,9 +70,9 @@ else
|
||||
DeployMac="1"
|
||||
DeployWin="1"
|
||||
DeployWin64="1"
|
||||
DeployWinArm="0"
|
||||
DeployWinArm="1"
|
||||
DeployLinux="1"
|
||||
echo "Deploying four versions of $AppVersionStrFull: for Windows 32 bit, Windows 64 bit, macOS and Linux 64 bit.."
|
||||
echo "Deploying five versions of $AppVersionStrFull: for Windows 32 bit / 64 bit / on ARM, macOS and Linux 64 bit.."
|
||||
fi
|
||||
if [ "$BuildTarget" == "mac" ]; then
|
||||
BackupPath="$HOME/Projects/backup/tdesktop"
|
||||
@@ -102,7 +102,7 @@ Win64RemoteFolder="tx64"
|
||||
WinArmDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tarm64"
|
||||
WinArmUpdateFile="tarm64upd$AppVersion"
|
||||
WinArmSetupFile="tsetup-arm64.$AppVersionStrFull.exe"
|
||||
WinArmPortablefile="tportable-arm64.$AppVersionStrFull.zip"
|
||||
WinArmPortableFile="tportable-arm64.$AppVersionStrFull.zip"
|
||||
WinArmRemoteFolder="tarm64"
|
||||
LinuxDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tlinux"
|
||||
LinuxUpdateFile="tlinuxupd$AppVersion"
|
||||
@@ -138,7 +138,7 @@ if [ "$AlphaVersion" != "0" ]; then
|
||||
Win64UpdateFile="${Win64UpdateFile}_${AlphaSignature}"
|
||||
Win64PortableFile="talpha${AlphaVersion}_${AlphaSignature}.zip"
|
||||
WinArmUpdateFile="${WinArmUpdateFile}_${AlphaSignature}"
|
||||
WinArmPortablefile="talpha${AlphaVersion}_${AlphaSignature}.zip"
|
||||
WinArmPortableFile="talpha${AlphaVersion}_${AlphaSignature}.zip"
|
||||
LinuxUpdateFile="${LinuxUpdateFile}_${AlphaSignature}"
|
||||
LinuxSetupFile="talpha${AlphaVersion}_${AlphaSignature}.tar.xz"
|
||||
fi
|
||||
|
||||
@@ -42,8 +42,8 @@ SignTool=sha256
|
||||
#define ArchModulesFolder "arm64"
|
||||
AppVerName={#MyAppName} {#MyAppVersion} arm64
|
||||
#elif MyBuildTarget == "win64"
|
||||
ArchitecturesAllowed="x64 arm64"
|
||||
ArchitecturesInstallIn64BitMode="x64 arm64"
|
||||
ArchitecturesAllowed="x64compatible"
|
||||
ArchitecturesInstallIn64BitMode="x64compatible"
|
||||
OutputBaseFilename=tsetup-x64.{#MyAppVersionFull}
|
||||
#define ArchModulesFolder "x64"
|
||||
AppVerName={#MyAppName} {#MyAppVersion} 64bit
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 5007002
|
||||
AppVersion 5007003
|
||||
AppVersionStrMajor 5.7
|
||||
AppVersionStrSmall 5.7.2
|
||||
AppVersionStr 5.7.2
|
||||
BetaChannel 0
|
||||
AppVersionStrSmall 5.7.3
|
||||
AppVersionStr 5.7.3
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 5.7.2
|
||||
AppVersionOriginal 5.7.3.beta
|
||||
|
||||
@@ -421,6 +421,10 @@ PRIVATE
|
||||
ui/widgets/fields/time_part_input_with_placeholder.cpp
|
||||
ui/widgets/fields/time_part_input_with_placeholder.h
|
||||
|
||||
ui/widgets/chat_filters_tabs_slider.cpp
|
||||
ui/widgets/chat_filters_tabs_slider.h
|
||||
ui/widgets/chat_filters_tabs_slider_reorder.cpp
|
||||
ui/widgets/chat_filters_tabs_slider_reorder.h
|
||||
ui/widgets/color_editor.cpp
|
||||
ui/widgets/color_editor.h
|
||||
ui/widgets/continuous_sliders.cpp
|
||||
|
||||
Submodule Telegram/lib_base updated: 21d1ac8bfc...2b622fd0b2
Submodule Telegram/lib_ui updated: 00b64a9311...b969b5bd32
@@ -1,3 +1,8 @@
|
||||
5.7.3 beta (13.11.24)
|
||||
|
||||
- Option to show folders above the chats list.
|
||||
- Folders on top of recipients list box.
|
||||
|
||||
5.7.2 (05.11.24)
|
||||
|
||||
- Fix recompressed video playback cutoff.
|
||||
|
||||
Reference in New Issue
Block a user