From 8cc3eb4bfed06195d971885d11430229b48cc1d5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 10:22:27 +0200 Subject: [PATCH 001/104] Change default URL scheme to https. Fixes #26499. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 2631d64a5..18ac9868b 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 2631d64a5f11a084457210fe2419f42a5f15f7ae +Subproject commit 18ac9868bb24801c6d4bf7bd5d1e6197add2f4b4 From 58c91be156960fda0e1a010060c32389a71c0107 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 11:41:58 +0200 Subject: [PATCH 002/104] Fix location links with Bing Maps uninstalled. Fixes #26506. --- Telegram/SourceFiles/platform/win/specific_win.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index f8c3e8074..0da6adae9 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -683,7 +683,10 @@ bool psLaunchMaps(const Data::LocationPoint &point) { AT_URLPROTOCOL, AL_EFFECTIVE, handler.put()); - if (FAILED(result) || !handler) { + if (FAILED(result) + || !handler + || !handler.data() + || std::wstring(handler.data()) == L"bingmaps") { return false; } From ff2df4b1e5f55014990775761d64305fb51db02a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 12:53:10 +0200 Subject: [PATCH 003/104] Beta version 4.8.12. (macOS only) - Fix crash in file attachments by a full rebuild on macOS. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 4 ++++ 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 0fabf4cf8..05e0aa48e 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.8.12.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 96d019b4e..8b33fbbbd 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,8,11,0 - PRODUCTVERSION 4,8,11,0 + FILEVERSION 4,8,12,0 + PRODUCTVERSION 4,8,12,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.8.11.0" + VALUE "FileVersion", "4.8.12.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.8.11.0" + VALUE "ProductVersion", "4.8.12.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 14efb90a5..c86c3585e 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,8,11,0 - PRODUCTVERSION 4,8,11,0 + FILEVERSION 4,8,12,0 + PRODUCTVERSION 4,8,12,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", "4.8.11.0" + VALUE "FileVersion", "4.8.12.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.8.11.0" + VALUE "ProductVersion", "4.8.12.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index cee5c672d..13716a5c1 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -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 = 4008011; -constexpr auto AppVersionStr = "4.8.11"; +constexpr auto AppVersion = 4008012; +constexpr auto AppVersionStr = "4.8.12"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 41c79ecaa..a9da6aa89 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4008011 +AppVersion 4008012 AppVersionStrMajor 4.8 -AppVersionStrSmall 4.8.11 -AppVersionStr 4.8.11 +AppVersionStrSmall 4.8.12 +AppVersionStr 4.8.12 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 4.8.11.beta +AppVersionOriginal 4.8.12.beta diff --git a/changelog.txt b/changelog.txt index 7fd07c141..c0521d01f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +4.8.12 beta (11.08.23) + +- Fix crash by a full rebuild on macOS. + 4.8.11 beta (10.08.23) - Fix initial video playback speed. From 238d4b8e173f0f5bf61ed1d6960fe6edf65f0d04 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 15 Aug 2023 00:20:59 +0400 Subject: [PATCH 004/104] Build OpenAL in RelWithDebInfo mode on macOS --- Telegram/build/prepare/prepare.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 1b8a37c47..86f88dc49 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1077,6 +1077,7 @@ mac: cd openal-soft git checkout 716f5373cb CFLAGS=$UNGUARDED CPPFLAGS=$UNGUARDED cmake -B build . \\ + -D CMAKE_BUILD_TYPE=RelWithDebInfo \\ -D CMAKE_INSTALL_PREFIX:PATH=$USED_PREFIX \\ -D ALSOFT_EXAMPLES=OFF \\ -D ALSOFT_UTILS=OFF \\ From 717041a462b430f13d9bc1a900f29a9e1f9ba8fb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 11 Aug 2023 15:23:27 +0300 Subject: [PATCH 005/104] Added ripple animation to message replies. --- .../history/history_item_components.cpp | 20 +++++- .../history/history_item_components.h | 6 ++ .../history/view/history_view_message.cpp | 62 ++++++++++++++++++- .../history/view/history_view_message.h | 3 +- .../history/view/media/history_view_gif.cpp | 12 +++- .../media/history_view_media_unwrapped.cpp | 14 ++++- 6 files changed, 111 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 05289283e..dc648b811 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -508,6 +508,8 @@ void HistoryMessageReply::paint( const auto stm = context.messageStyle(); { + const auto opacity = p.opacity(); + const auto outerWidth = w + 2 * x; const auto &bar = !inBubble ? st->msgImgReplyBarColor() : replyToColorKey @@ -518,8 +520,22 @@ void HistoryMessageReply::paint( y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), - w + 2 * x); - const auto opacity = p.opacity(); + outerWidth); + + if (ripple.animation) { + const auto colorOverride = &stm->msgWaveformInactive->c; + p.setOpacity(st::historyPollRippleOpacity); + ripple.animation->paint( + p, + x - st::msgReplyPadding.left(), + y, + outerWidth, + colorOverride); + if (ripple.animation->empty()) { + ripple.animation.reset(); + } + } + p.setOpacity(opacity * kBarAlpha); p.fillRect(rbar, bar); p.setOpacity(opacity); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 5a8db07c9..e743932b9 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "spellcheck/spellcheck_types.h" // LanguageId. #include "ui/empty_userpic.h" #include "ui/effects/animations.h" +#include "ui/effects/ripple_animation.h" #include "ui/chat/message_bubble.h" struct WebPageData; @@ -307,6 +308,11 @@ struct HistoryMessageReply bool topicPost = false; bool storyReply = false; + struct final { + mutable std::unique_ptr animation; + QPoint lastPoint; + } ripple; + }; struct HistoryMessageTranslation diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ad06f53c4..d3ea404c6 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1600,6 +1600,8 @@ void Message::clickHandlerPressedChanged( toggleTopicButtonRipple(pressed); } else if (_viewButton) { _viewButton->checkLink(handler, pressed); + } else if (const auto reply = displayedReply()) { + toggleReplyRipple(pressed); } } @@ -1639,6 +1641,58 @@ void Message::toggleRightActionRipple(bool pressed) { } } +void Message::toggleReplyRipple(bool pressed) { + const auto reply = displayedReply(); + if (!reply) { + return; + } + + if (pressed) { + if (!reply->ripple.animation && !unwrapped()) { + const auto smallTop = displayFromName() + || displayedTopicButton() + || displayForwardedFrom(); + const auto rounding = countBubbleRounding(); + + using Corner = Ui::BubbleCornerRounding; + using Radius = Ui::CachedCornerRadius; + const auto &small = Ui::CachedCornersMasks(Radius::ThumbSmall); + const auto &large = Ui::CachedCornersMasks(Radius::ThumbLarge); + const auto corners = std::array{{ + ((smallTop || (rounding.topLeft == Corner::Small)) + ? small + : large)[0], + ((smallTop || (rounding.topRight == Corner::Small)) + ? small + : large)[1], + small[2], + small[3], + }}; + + const auto &padding = st::msgReplyPadding; + const auto geometry = countGeometry(); + const auto size = QSize( + geometry.width() + - padding.left() / 2 + - padding.right(), + st::msgReplyBarSize.height() + + padding.top() + + padding.bottom()); + reply->ripple.animation = std::make_unique( + st::defaultRippleAnimation, + Images::Round( + Ui::RippleAnimation::MaskByDrawer(size, true, nullptr), + corners), + [=] { repaint(); }); + } + if (reply->ripple.animation) { + reply->ripple.animation->add(reply->ripple.lastPoint); + } + } else if (reply->ripple.animation) { + reply->ripple.animation->lastStop(); + } +} + BottomRippleMask Message::bottomRippleMask(int buttonHeight) const { using namespace Ui; using namespace Images; @@ -2268,9 +2322,15 @@ bool Message::getStateReplyInfo( if (auto reply = displayedReply()) { int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); if (point.y() >= trect.top() && point.y() < trect.top() + h) { + const auto g = QRect( + trect.x(), + trect.y() + st::msgReplyPadding.top(), + trect.width(), + st::msgReplyBarSize.height()); if ((reply->replyToMsg || reply->replyToStory) - && QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) { + && g.contains(point)) { outResult->link = reply->replyToLink(); + reply->ripple.lastPoint = point - g.topLeft(); } return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index f9fd8afa7..2c0ebaf0e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -187,7 +187,8 @@ private: void createTopicButtonRipple(); void toggleRightActionRipple(bool pressed); - void createRightActionRipple(); + + void toggleReplyRipple(bool pressed); void paintCommentsButton( Painter &p, diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 0bee2fbf0..074f89dab 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1064,8 +1064,18 @@ TextState Gif::textState(QPoint point, StateRequest request) const { recth -= skip; } if (reply) { - if (QRect(rectx, recty, rectw, recth).contains(point)) { + const auto replyRect = QRect(rectx, recty, rectw, recth); + if (replyRect.contains(point)) { result.link = reply->replyToLink(); + reply->ripple.lastPoint = point - replyRect.topLeft(); + if (!reply->ripple.animation) { + reply->ripple.animation = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + replyRect.size(), + st::roundRadiusSmall), + [=] { item->history()->owner().requestItemRepaint(item); }); + } return result; } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index cd3674de1..3348a1855 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_media_unwrapped.h" +#include "data/data_session.h" +#include "history/history.h" #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_sticker.h" #include "history/view/history_view_element.h" @@ -460,8 +462,18 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { recth -= skip; } if (reply) { - if (QRect(rectx, recty, rectw, recth).contains(point)) { + const auto replyRect = QRect(rectx, recty, rectw, recth); + if (replyRect.contains(point)) { result.link = reply->replyToLink(); + reply->ripple.lastPoint = point - replyRect.topLeft(); + if (!reply->ripple.animation) { + reply->ripple.animation = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + replyRect.size(), + st::roundRadiusSmall), + [=] { item->history()->owner().requestItemRepaint(item); }); + } return result; } } From 8a24f33c062d63920736e7877b01450766adb972 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 11 Aug 2023 16:27:17 +0300 Subject: [PATCH 006/104] Added warning of limitation on excluding chats from filter from menu. --- .../SourceFiles/boxes/choose_filter_box.cpp | 31 ++++++++++--------- .../SourceFiles/boxes/choose_filter_box.h | 4 ++- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 416b30ff1..181dccb1e 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -70,8 +70,6 @@ void ChangeFilterById( MTP_int(filter.id()), filter.tl() )).done([=, chat = history->peer->name(), name = filter.title()] { - // Since only the primary window has dialogs list, - // We can safely show toast there. const auto account = &history->session().account(); if (const auto controller = Core::App().windowFor(account)) { controller->showToast((add @@ -120,17 +118,19 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const { } ChooseFilterValidator::LimitData ChooseFilterValidator::limitReached( - FilterId filterId) const { + FilterId filterId, + bool always) const { Expects(filterId != 0); const auto list = _history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); const auto limit = _history->owner().pinnedChatsLimit(filterId); + const auto &chatsList = always ? i->always() : i->never(); return { .reached = (i != end(list)) - && !ranges::contains(i->always(), _history) - && (i->always().size() >= limit), - .count = int(i->always().size()), + && !ranges::contains(chatsList, _history) + && (chatsList.size() >= limit), + .count = int(chatsList.size()), }; } @@ -156,18 +156,21 @@ void FillChooseFilterMenu( const auto contains = filter.contains(history); const auto action = menu->addAction(filter.title(), [=] { - if (filter.contains(history)) { - if (validator.canRemove(id)) { - validator.remove(id); - } - } else if (const auto r = validator.limitReached(id); r.reached) { + const auto toAdd = !filter.contains(history); + const auto r = validator.limitReached(id, toAdd); + if (r.reached) { controller->show(Box( FilterChatsLimitBox, &controller->session(), r.count, - true)); - } else if (validator.canAdd()) { - validator.add(id); + toAdd)); + return; + } else if (toAdd ? validator.canAdd() : validator.canRemove(id)) { + if (toAdd) { + validator.add(id); + } else { + validator.remove(id); + } } }, contains ? &st::mediaPlayerMenuCheck : nullptr); action->setEnabled(contains diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.h b/Telegram/SourceFiles/boxes/choose_filter_box.h index 8e32b267c..e6c5ad335 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.h +++ b/Telegram/SourceFiles/boxes/choose_filter_box.h @@ -27,7 +27,9 @@ public: [[nodiscard]] bool canAdd() const; [[nodiscard]] bool canRemove(FilterId filterId) const; - [[nodiscard]] LimitData limitReached(FilterId filterId) const; + [[nodiscard]] LimitData limitReached( + FilterId filterId, + bool always) const; void add(FilterId filterId) const; void remove(FilterId filterId) const; From 5575d50277d46d986b0098ce0c535365683c60d0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Jul 2023 18:57:51 +0200 Subject: [PATCH 007/104] Update API scheme to layer161. --- .../SourceFiles/api/api_blocked_peers.cpp | 3 ++ Telegram/SourceFiles/api/api_updates.cpp | 2 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 33 ++++++++++++------- Telegram/SourceFiles/mtproto/scheme/layer.tl | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp index bf3a061e8..9dfc886b1 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.cpp +++ b/Telegram/SourceFiles/api/api_blocked_peers.cpp @@ -79,6 +79,7 @@ void BlockedPeers::block(not_null peer) { Data::PeerUpdate::Flag::IsBlocked); } else if (_blockRequests.find(peer) == end(_blockRequests)) { const auto requestId = _api.request(MTPcontacts_Block( + MTP_flags(0), peer->input )).done([=] { _blockRequests.erase(peer); @@ -111,6 +112,7 @@ void BlockedPeers::unblock( return; } const auto requestId = _api.request(MTPcontacts_Unblock( + MTP_flags(0), peer->input )).done([=] { _blockRequests.erase(peer); @@ -163,6 +165,7 @@ void BlockedPeers::request(int offset, Fn onDone) { return; } _requestId = _api.request(MTPcontacts_GetBlocked( + MTP_flags(0), MTP_int(offset), MTP_int(offset ? kBlockedPerPage : kBlockedFirstSlice) )).done([=](const MTPcontacts_Blocked &result) { diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 3d7584a64..c2a1399da 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1987,7 +1987,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePeerBlocked: { const auto &d = update.c_updatePeerBlocked(); if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer_id()))) { - peer->setIsBlocked(mtpIsTrue(d.vblocked())); + peer->setIsBlocked(d.is_blocked()); } } break; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 9c54c97bc..c2e1a6481 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -221,7 +221,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#4fe1cc86 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?UserStories = UserFull; +userFull#4fe1cc86 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?UserStories = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -350,7 +350,7 @@ updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updateChannelMessageForwards#d29a27f4 channel_id:long id:int forwards:int = Update; updateReadChannelDiscussionInbox#d6b19546 flags:# channel_id:long top_msg_id:int read_max_id:int broadcast_id:flags.0?long broadcast_post:flags.0?int = Update; updateReadChannelDiscussionOutbox#695c9e7c channel_id:long top_msg_id:int read_max_id:int = Update; -updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; +updatePeerBlocked#ebe07752 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true peer_id:Peer = Update; updateChannelUserTyping#8c88c923 flags:# channel_id:long top_msg_id:flags.0?int from_id:Peer action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; updatePinnedChannelMessages#5bb98608 flags:# pinned:flags.0?true channel_id:long messages:Vector pts:int pts_count:int = Update; @@ -385,6 +385,7 @@ updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; updateStory#205a4133 user_id:long story:StoryItem = Update; updateReadStories#feb5345a user_id:long max_id:int = Update; updateStoryID#1bf335b9 id:int random_id:long = Update; +updateStoriesStealthMode#2c084dc1 stealth_mode:StoriesStealthMode = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1530,18 +1531,18 @@ storyViews#d36760cf flags:# views_count:int recent_viewers:flags.0?Vector storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#562aa637 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia privacy:flags.2?Vector views:flags.3?StoryViews = StoryItem; +storyItem#945953ba flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews = StoryItem; userStories#8611a200 flags:# user_id:long max_read_id:flags.0?int stories:Vector = UserStories; -stories.allStoriesNotModified#47e0a07e state:string = stories.AllStories; -stories.allStories#839e0428 flags:# has_more:flags.0?true count:int state:string user_stories:Vector users:Vector = stories.AllStories; +stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; +stories.allStories#519d899e flags:# has_more:flags.0?true count:int state:string user_stories:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; stories.stories#4fe57df1 count:int stories:Vector users:Vector = stories.Stories; stories.userStories#37a6ff5f stories:UserStories users:Vector = stories.UserStories; -storyView#a71aacc2 user_id:long date:int = StoryView; +storyView#d0b0a7de flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int = StoryView; stories.storyViewsList#fb3f77ac count:int views:Vector users:Vector = stories.StoryViewsList; @@ -1552,6 +1553,14 @@ inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; +storiesStealthMode#712e27fd flags:# active_until_date:flags.0?int cooldown_until_date:flags.1?int = StoriesStealthMode; + +mediaAreaCoordinates#3d1ea4e x:double y:double w:double h:double rotation:double = MediaAreaCoordinates; + +mediaAreaVenue#be82db9c coordinates:MediaAreaCoordinates geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MediaArea; +inputMediaAreaVenue#b282217f coordinates:MediaAreaCoordinates query_id:long result_id:string = MediaArea; +mediaAreaGeoPoint#df8b3b22 coordinates:MediaAreaCoordinates geo:GeoPoint = MediaArea; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1685,9 +1694,9 @@ contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; -contacts.block#68cc1411 id:InputPeer = Bool; -contacts.unblock#bea65d50 id:InputPeer = Bool; -contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; +contacts.block#2e2e8734 flags:# my_stories_from:flags.0?true id:InputPeer = Bool; +contacts.unblock#b550d328 flags:# my_stories_from:flags.0?true id:InputPeer = Bool; +contacts.getBlocked#9a868f80 flags:# my_stories_from:flags.0?true offset:int limit:int = contacts.Blocked; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:long = contacts.TopPeers; @@ -1704,6 +1713,7 @@ contacts.exportContactToken#f8654027 = ExportedContactToken; contacts.importContactToken#13005788 token:string = User; contacts.editCloseFriends#ba6705f0 id:Vector = Bool; contacts.toggleStoriesHidden#753fb865 id:InputUser hidden:Bool = Bool; +contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector limit:int = Bool; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; @@ -2088,8 +2098,8 @@ chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool; chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector; chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; -stories.sendStory#424cd47a flags:# pinned:flags.2?true noforwards:flags.4?true media:InputMedia caption:flags.0?string entities:flags.1?Vector privacy_rules:Vector random_id:long period:flags.3?int = Updates; -stories.editStory#2aae7a41 flags:# id:int media:flags.0?InputMedia caption:flags.1?string entities:flags.1?Vector privacy_rules:flags.2?Vector = Updates; +stories.sendStory#d455fcec flags:# pinned:flags.2?true noforwards:flags.4?true media:InputMedia media_areas:flags.5?Vector caption:flags.0?string entities:flags.1?Vector privacy_rules:Vector random_id:long period:flags.3?int = Updates; +stories.editStory#a9b91ae4 flags:# id:int media:flags.0?InputMedia media_areas:flags.3?Vector caption:flags.1?string entities:flags.1?Vector privacy_rules:flags.2?Vector = Updates; stories.deleteStories#b5d501d7 id:Vector = Vector; stories.togglePinned#51602944 id:Vector pinned:Bool = Vector; stories.getAllStories#eeb0d625 flags:# next:flags.1?true hidden:flags.2?true state:flags.0?string = stories.AllStories; @@ -2105,3 +2115,4 @@ stories.getStoryViewsList#4b3b5e97 id:int offset_date:int offset_id:long limit:i stories.getStoriesViews#9a75d6a6 id:Vector = stories.StoryViews; stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink; stories.report#c95be06a user_id:InputUser id:Vector reason:ReportReason message:string = Bool; +stories.activateStealthMode#11d7ddae flags:# past:flags.0?true future:flags.1?true = Bool; diff --git a/Telegram/SourceFiles/mtproto/scheme/layer.tl b/Telegram/SourceFiles/mtproto/scheme/layer.tl index 5e910a52b..2f8c6ce82 100644 --- a/Telegram/SourceFiles/mtproto/scheme/layer.tl +++ b/Telegram/SourceFiles/mtproto/scheme/layer.tl @@ -1 +1 @@ -// LAYER 160 +// LAYER 161 From c12297d8cb7a3cf16c6ebefdb32ce2072f0879a3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Aug 2023 19:09:09 +0200 Subject: [PATCH 008/104] Implement stealth mode in stories. --- Telegram/CMakeLists.txt | 2 + .../icons/mediaview/download_locked.png | Bin 0 -> 508 bytes .../icons/mediaview/download_locked@2x.png | Bin 0 -> 915 bytes .../icons/mediaview/download_locked@3x.png | Bin 0 -> 1313 bytes .../Resources/icons/menu/download_locked.png | Bin 0 -> 516 bytes .../icons/menu/download_locked@2x.png | Bin 0 -> 870 bytes .../icons/menu/download_locked@3x.png | Bin 0 -> 1226 bytes Telegram/Resources/icons/menu/stealth.png | Bin 0 -> 635 bytes Telegram/Resources/icons/menu/stealth@2x.png | Bin 0 -> 1237 bytes Telegram/Resources/icons/menu/stealth@3x.png | Bin 0 -> 1762 bytes .../Resources/icons/menu/stealth_locked.png | Bin 0 -> 726 bytes .../icons/menu/stealth_locked@2x.png | Bin 0 -> 1457 bytes .../icons/menu/stealth_locked@3x.png | Bin 0 -> 2123 bytes .../stories_next.png => stories/next.png} | Bin .../next@2x.png} | Bin .../next@3x.png} | Bin .../Resources/icons/stories/stealth_25m.png | Bin 0 -> 885 bytes .../icons/stories/stealth_25m@2x.png | Bin 0 -> 1778 bytes .../icons/stories/stealth_25m@3x.png | Bin 0 -> 2566 bytes .../Resources/icons/stories/stealth_5m.png | Bin 0 -> 791 bytes .../Resources/icons/stories/stealth_5m@2x.png | Bin 0 -> 1568 bytes .../Resources/icons/stories/stealth_5m@3x.png | Bin 0 -> 2309 bytes .../Resources/icons/stories/stealth_logo.png | Bin 0 -> 1463 bytes .../icons/stories/stealth_logo@2x.png | Bin 0 -> 2836 bytes .../icons/stories/stealth_logo@3x.png | Bin 0 -> 4385 bytes Telegram/Resources/langs/lang.strings | 18 + Telegram/SourceFiles/api/api_updates.cpp | 5 + Telegram/SourceFiles/data/data_stories.cpp | 33 ++ Telegram/SourceFiles/data/data_stories.h | 15 + .../stories/media_stories_controller.cpp | 12 + .../media/stories/media_stories_controller.h | 3 + .../media/stories/media_stories_stealth.cpp | 407 ++++++++++++++++++ .../media/stories/media_stories_stealth.h | 18 + .../media/stories/media_stories_view.cpp | 8 + .../media/stories/media_stories_view.h | 3 + .../SourceFiles/media/view/media_view.style | 74 +++- .../media/view/media_view_overlay_widget.cpp | 11 +- Telegram/SourceFiles/ui/menu_icons.style | 2 + 38 files changed, 608 insertions(+), 3 deletions(-) create mode 100644 Telegram/Resources/icons/mediaview/download_locked.png create mode 100644 Telegram/Resources/icons/mediaview/download_locked@2x.png create mode 100644 Telegram/Resources/icons/mediaview/download_locked@3x.png create mode 100644 Telegram/Resources/icons/menu/download_locked.png create mode 100644 Telegram/Resources/icons/menu/download_locked@2x.png create mode 100644 Telegram/Resources/icons/menu/download_locked@3x.png create mode 100644 Telegram/Resources/icons/menu/stealth.png create mode 100644 Telegram/Resources/icons/menu/stealth@2x.png create mode 100644 Telegram/Resources/icons/menu/stealth@3x.png create mode 100644 Telegram/Resources/icons/menu/stealth_locked.png create mode 100644 Telegram/Resources/icons/menu/stealth_locked@2x.png create mode 100644 Telegram/Resources/icons/menu/stealth_locked@3x.png rename Telegram/Resources/icons/{mediaview/stories_next.png => stories/next.png} (100%) rename Telegram/Resources/icons/{mediaview/stories_next@2x.png => stories/next@2x.png} (100%) rename Telegram/Resources/icons/{mediaview/stories_next@3x.png => stories/next@3x.png} (100%) create mode 100644 Telegram/Resources/icons/stories/stealth_25m.png create mode 100644 Telegram/Resources/icons/stories/stealth_25m@2x.png create mode 100644 Telegram/Resources/icons/stories/stealth_25m@3x.png create mode 100644 Telegram/Resources/icons/stories/stealth_5m.png create mode 100644 Telegram/Resources/icons/stories/stealth_5m@2x.png create mode 100644 Telegram/Resources/icons/stories/stealth_5m@3x.png create mode 100644 Telegram/Resources/icons/stories/stealth_logo.png create mode 100644 Telegram/Resources/icons/stories/stealth_logo@2x.png create mode 100644 Telegram/Resources/icons/stories/stealth_logo@3x.png create mode 100644 Telegram/SourceFiles/media/stories/media_stories_stealth.cpp create mode 100644 Telegram/SourceFiles/media/stories/media_stories_stealth.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8fb9e62d1..10b47447d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -997,6 +997,8 @@ PRIVATE media/stories/media_stories_sibling.h media/stories/media_stories_slider.cpp media/stories/media_stories_slider.h + media/stories/media_stories_stealth.cpp + media/stories/media_stories_stealth.h media/stories/media_stories_view.cpp media/stories/media_stories_view.h media/streaming/media_streaming_audio_track.cpp diff --git a/Telegram/Resources/icons/mediaview/download_locked.png b/Telegram/Resources/icons/mediaview/download_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..206d5893d729884b441805a2d3a6475091642d51 GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfEX~u!F~mYJ zIYA<)VI~jz%U7?WqN6{5{AgSKjc46l&U1&3&fsS&h}6~94G+H_zCLc|j2RJ;kq3`P zKG2EbS@8En(5X|W)cxn#RDF5z`}=!eU*ATa4xPm23BHMmiHyHGX3mt%XqhlUfL-Fx zkB`hN+Dw{ra&tpNLmO}L@bD;zRa924jo!|8LQTd^fL9oHa0fSoH=uI+F7T|Wlv5_f`#3-32 z1s*$b;sz6&1Dn`ON8Of337VRk%F4=0cdOssv3&B0!&O{mmcl~s2`pM}*Ll4S axyhh%Z~eT<3Ud-bA@Awx=d#Wzp$P!M#;?%; literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/mediaview/download_locked@2x.png b/Telegram/Resources/icons/mediaview/download_locked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..46bac90860ae390508fd44db5db7675920183b35 GIT binary patch literal 915 zcmV;E18n?>P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NE{YgYYR9Fe^SUYPfK@`?|uQ4$X z(G)>QB^GJK!U#bERz3=?EK&sR#KOkPVE%%Qt&%#VFnv_)0s$2pg%r_59n zAosgj7>3o|tZ}!vmz_esIrE)!zCANLGpj-&H@O0^`3lYWn>?h!AA6 z*}PuwLb3=`c7A^DbUJr-cJRbEH#e*_N~JOw460PBLZOh!WGa=))6r7Ar=p4JdyBdw=_4W1k_Li}?TCMy0dtfXnn#o`h1kDGfE!*4M=%Nys zDPn(qeg;NSiG={k1(8ew!7qWl@6qs*I~EYoaj%eH&JRT42&x4`n>guZ9ZsP*9unmX9Y&Ki3*Li|09)c7U7KafU zj`Wk06PlMwrI;ZA+WtK(s6v=8h+yth*fuUNFT>$5e?AStn&|?dONKzx>-DSV6XK6upwVcM#E*}U3?=|w5I_?WO&ktR@@ln8G_e4NKoBRB z@F+T+?(Xi6B?Vm=F(f>Wn9R-1&0H==YeE-BF>@>%3)jNy>uW|60P`uhY!LDJ$Dd97 zxN=0f0{=q=IR76~tJTqHREq5%CrmP##FcHi`kkGf(L0{F7{R&J2)dwgIZI*Z{I*9` pkx#lQlj33Im~sW=3jC85_yfR_5F!Z|4|)Iq002ovPDHLkV1fX3jwb*B literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/mediaview/download_locked@3x.png b/Telegram/Resources/icons/mediaview/download_locked@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..57752e830b034464a028bd975d7eb3abccd427c3 GIT binary patch literal 1313 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbHUhz|!sM;uw;_ z`Zmg2qt;R4`;L3-RXANgbxi%9yfDH;(e+evqV|+c(>86KvgwbycA|6Q;X_SMPiD3X zX}`CboIdY={Qs4ivn;>g+4;Qa{U6ix!r#wkzyJO0`|q@KJB{yK#>M@*BzXZ0f7~}p z*nD%(-o103-p<(?yfD_hud7Q-SJ&6ikB^V<-;W;_*4EMS@%QiDo7VZpE=lESe0;po zhJ@gVh={PTXV0G>zxn?Cdw+lb`}gk`SFL+_cx(NNn>TM(R##US7c)Dib-a1=rmKr< zqG(NUaImzLR85VHLizEY)YR16++0Ds85`%!k@@^oL{RYJwQFJ$5**@BlJfH2J$a&{ zqobp*&%e+^Mnha&yff#=>xS(M`!}Dub0_9i6YEK{mGeZoxVSj9*K9v{@ZjCMcX#jJ z{rK_YlP6C;eVUpS855IZm-$ZN+3VM*A3d9uBR)}BSh$lx>iqfhYYzVT^XK_O6`u)4QQRRUETlE#LJp5-qefreUz|gd5)22zy zr_HjnvyF|6KBWKs^5w{@&NUZ;e7wC^uUofljmw%vYPPnrUtDZWxCFJew2WFZ)*W$U ziT1karkHf`;zcDp$=-#FosPT+j%(w|R<=vHuv)-SZ1w9+YR{_p?n*nJpHcn!?XQge ziN|lOFW-AVS)g6;S0yMuUjpW9UUE4uU=jMFV@=H zT2oUqCm`GCQAB~u%U7R11(j^^;F&6x45nwgn}g@y54pSsrq z7y-bred2TWW<*Taom+=BYZDU}u3D9qnb|4ru#eMg^XAP!Z+`svk#GAUgMGVpSy@>* z?G3EsI6m#hzcXjfC|Fh7+Sw(kxjvsZP3#(v@5N`gO~s}wrO7{MozoiOu;RL(udk_@ z*(NXllVY8W?@pigj*Y$h;e!FUtm?Um6DMA~78aJNcm2wh1lGRZ-iJ16c`vWUg@mxN zvE7*J_GH~Uy{^M4numIRJ!(66cv_XF;+HR90%}5Dyne0x;EiB2qp6xxUEMyRng{_| zS=k*QEfzRCJ6q^<9$DZbp5Bx0pOT^?9W1(Y=gyyR-}W|J8ya4`cki6m#g<7|9om%R zOjjIoIntQL({)KEL*#xW?{ydPmN=0#|i?wn{7=r_>=ypoooMc z=*+{@;?;{Q-1@ujKYX~*(p=)e8RH)@lBSA^iTU~G<;+VC9+>)L-WS)S`NhS-PuP|w z8eW(jF;~{w#zscEewy^G04r&C*Xo=pzkdHNJa?4UacAx1F3Za^O4wkqE}bo9f1R`L z%H6xI_4Lj)XIj|WzJ2@l=<=ldZH!ARYmOXw_uWR7x$Q|c)5={+UG8o7UFxErEn#MC zOHuXil6dt}W1C0+zvi|EMgt>ekG)eYEG$;6UmyNNj#)x7>6Tl%0}4Fwl;;m?KX2QG TZ5~@gK*f`%tDnm{r-UW|QbkN_ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/download_locked.png b/Telegram/Resources/icons/menu/download_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..824ca3707c41a7c4e18e00213dd1d891b59acc66 GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfEYH)$F~maf zZJ^=mrT~$1B9*#{@ASgF1v)f1FFg=(`M>DvR@)csF3$T8d{;hXzJ=A%nb*pCtJ=C} z7nJ8Uzc0|glNXhdpH*@H%iQ-h_bu-wFa4C2@c)D3-Ms52XRG$gwJCOr^G{0Mc`?H! zu*G<;U%G*W3?F-FXyrV6x&DatlP30`U}t)1Hrw}l>G$7%%Xa5(jdFR;QSGwo>T9FE z#}8a}PQTiv*Y!yH(9@#EoR11sj{EQDZ@;}jqpRub!-5wUd>>w$S$*g@ywOPahu#_+ za}DEGz4ts#i=UW3_K`VWB4H8u?#_u#Yn~Qy{=e~gk5A6_+jISvN3Cu9s^VF5|9nN> zsiR4ZjdAPQU#T+*_+D0zxhnuFEg4Y%nNzxo))5NZ2rBC)V%g9vC;^_Rxyz!%chKE9+Qq$a~ z-c}AbZbN~C+jpdYKV)#mV%@pq?z=l%=Tz^z{&9EtvFcEbCyM0`5$snE9qRA6_OhgG z_uZ_mdScyAi%yEJd}7pby4pr={&{v^kKK3kqi|NgyW*Y4fP$F-(rq6~_jeoy?OW(*b3+7>U5pa1b#M-gBeaDgqiu-r&j9eRbVH4AKF1(qpvist>vmXYo>)?OL4`s%v%>p9sDOi3tOVQaQl;lGcXbC&sR zU$zF%fUJ85SB7+XEUa&OGB=>*K#GxPc=JjQPu67ztcAnvco-U2J}KDA{6C5P+h5tq ze#;L(+~B7C>@M%|6E$}7-A8xKI;qJq-8<&tkx*wB2jRT0n{VbUS38&A-%w%Gw|e=G uSqqj0XcVlxsTrvDl<$P>1nUp;%O9vW%XCgS6>bm?O5>icelF{r5}E+YymW*B literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/download_locked@3x.png b/Telegram/Resources/icons/menu/download_locked@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..69d05e38d61f63b8b90a06ea3acd74de57783b43 GIT binary patch literal 1226 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbHUhz+&R*;uw;_ z`Zmf}yUbDI*+K75p&Xj0gagfjA_BxBI$9%s@GlbaR5|7LL|J~~{scv@O_N%Mybc{Z z=e|cygzIO``s-Ip%!}tdpYy!n%;$Z@Vc+i*-~AqQz4qKpqd&CShy`{qr=$u;XXJbLt~tE;P{ zgTwLI!-o$K9XfRP?%W6Bvl5-6C#E?%I{NzhM$BNey>qOmhv&}r@88RXx3NuB%TSNy z8aCMvp+rQu=a?74GoEi88I+-YfPIdlH}`1ttrhf9|(y^uNS z)SB}uXY#^>gDSp#+!9hV8(-U^9@LZOXo*~V| zf2*gnGx6-_&&99W|9<&WQd)X+&c=-!o%C~esr2x@y#DO`jnk*Qd7k-w)}P&VD8-Pm z;@>$NuhWnz5TSog^3&1tXcE!-Mdq#ymp81eB8ZzcUxVt`nGj7cJq5UYXp1#F5bFzE6|rJ z!042fm7Tiz_NScue05dT#Ntzb+`FIc*|B5B?AgixuUz4`E=ar)n7C>}YDx;r>$Zmv z6BT!_Rg0VY^y$;HXMLA^-7UiQQU)1-WTyZCS5hyqK_cCimfMqH?R2E>*R;SM;X8azSoY z)vT=7Z{CR9n8Cq3l4Gv_4)YjVe1|4eK*4fE< zN@%{txibM03?lZHmM1@+l00N{^YZ1x8mA6ja1B)qury>^xN)Q5sU6$5i!(S%%n@l8 zQ0!dfyT(c5{l0y6T0FkDUe}y_lqI@>YvCJzfB(qI(raE8Y<#pz#qP$13j$ZwZZi}V zOpt7DZr&vK%rSgIr{D`d9-b4o9*873-aPW1E4x6ApY88H)}?Y@R`&Mt-d-^VE8=hO zVuHawt%R>%r2V=d*l$|1rbo80$Ry3-pvAq$+%wOfrR{%y@6FQp@83@kcY9aua9AmS zb@UyJqC~^T+YjxV$Hph{sAcZ&?Hzp_ObZe>7KGh5Tyl7sd{~wJkG{8W-`>4bX2^kRg%?vPPK4egVS49o7OPJKu?iH0@M8#F21mWg|k!mB4?|U z$n#|NqT67#uS{btHW=_&&;hNiTzZLB~@?*!8O972y zdpT9~zeq)%xnxnTZ&tc=SHZ#)O9GzlBn9hi}5oGl6k?mggJr!L?m5R8Z6!D&}elF{r G5}E+_x&3?q literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/stealth@2x.png b/Telegram/Resources/icons/menu/stealth@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..50706a61f276fc95c6e33c38a0e486dff3a26573 GIT binary patch literal 1237 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 Ef{rysi2wiq literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/stealth_locked.png b/Telegram/Resources/icons/menu/stealth_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc0cb5e3061ac930b90420f022812bdd872febc GIT binary patch literal 726 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfyu$s#WBP} z@NS6j>Xm^acKc4cb9nw_K6SudGw-Csq92TQLCezI{xz&z$tXFoQRC-{BL{jKJA<4g zcX+0WFi-hxF}wEM>1x02*1PW7-gv+HdffTX>$ZNo_51DHc~%X$w=Ds4@NY_19lt>uosxxN!H~ z_}<4Kcf_r~zvD`lfy9UZVXIk1GhCGfceyrtEnPH&VQZ8x!~7Z*A{vfb*IZLFr&a{W(qR>+^~6khV&bPlvv4Lh}$~vPKuG_ug`fYqER|xcJuk=?6Vb_F1&YI zvvOg7)m}Ny-`2Vz%%+TgjnCwmSxfaENisBjSz;BVnxUw7P39pFSI3khW{bKlVQa&L zxmrt@qdK@P7sdH+buT_FDI&1)zw83V<(D(}-#^}3b+39&W}ny6O*eCzKHW&H+Zv^7 zCB1f{-|~-M2Il$NQ+l!<+U>P6(AgNl^YTGm*y^h(MxC$w7#p4@7#x_pAc{xob<_Fh z`|Xq~LKBW~Gjm=1HTipY=={VMu6s^xFYROZF#EfxP1RVp#u1dJJYD@<);T3K0RZwc BEII%H literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/stealth_locked@2x.png b/Telegram/Resources/icons/menu/stealth_locked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..979e23d5cd0f08ed07d7ad967d61e7af899639a8 GIT binary patch literal 1457 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 LNkvXXu0mjf?Vf(L literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/stealth_25m@2x.png b/Telegram/Resources/icons/stories/stealth_25m@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ec9a01f7a9fea5a69b67c02dc457808b57ef4a6 GIT binary patch literal 1778 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/stealth_logo.png b/Telegram/Resources/icons/stories/stealth_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..10a249195359081142f13c54869a6d5821ae580f GIT binary patch literal 1463 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 NDN5fAS*hz9`5)&SPB{Po literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/stealth_logo@3x.png b/Telegram/Resources/icons/stories/stealth_logo@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..161618d2026318b47932dcdebc96b91058f15d9e GIT binary patch literal 4385 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 38577fef1..e6dfeeb0a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3877,6 +3877,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_done_many#one" = "{count} story is hidden from your profile."; "lng_stories_archive_done_many#other" = "{count} stories are hidden from your profile."; +"lng_stealth_mode_menu_item" = "Stealth Mode"; +"lng_stealth_mode_title" = "Stealth Mode"; +"lng_stealth_mode_unlock_about" = "Subscribe to Telegram Premium to hide the fact that you viewed peoples' stories from them."; +"lng_stealth_mode_about" = "Turn Stealth Mode on to hide the fact that you viewed peoples' stories from them."; +"lng_stealth_mode_past_title" = "Hide Recent Views"; +"lng_stealth_mode_past_about" = "Hide my views in the last 5 minutes."; +"lng_stealth_mode_next_title" = "Hide Next Views"; +"lng_stealth_mode_next_about" = "Hide my views in the next 25 minutes."; +"lng_stealth_mode_unlock" = "Unlock Stealth Mode"; +"lng_stealth_mode_enable" = "Enable Stealth Mode"; +"lng_stealth_mode_cooldown_in" = "Available in {left}"; +"lng_stealth_mode_cooldown_tip" = "Please wait until the **Stealth Mode** is ready to use again."; +"lng_stealth_mode_enabled_tip_title" = "Stealth Mode On"; +"lng_stealth_mode_enabled_tip" = "The creators of stories you viewed in the last **5 minutes** or will view in the next **25 minutes** won't see you in the viewers' lists."; +"lng_stealth_mode_countdown" = "Stealth Mode active – {left}"; +"lng_stealth_mode_already_title" = "You are in Stealth Mode"; +"lng_stealth_mode_already_about" = "The creators of stories you will view in the next **{left}** won't see you in the viewers' lists."; + "lng_stories_link_invalid" = "This link is broken or has expired."; // Wnd specific diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index c2a1399da..78b286db0 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2528,6 +2528,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { _session->data().stories().apply(update.c_updateReadStories()); } break; + case mtpc_updateStoriesStealthMode: { + const auto &data = update.c_updateStoriesStealthMode(); + _session->data().stories().apply(data.vstealth_mode()); + } break; + } } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 085958ea2..8aa84afd2 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -174,6 +174,14 @@ void Stories::apply(const MTPDupdateReadStories &data) { bumpReadTill(peerFromUser(data.vuser_id()), data.vmax_id().v); } +void Stories::apply(const MTPStoriesStealthMode &stealthMode) { + const auto &data = stealthMode.data(); + _stealthMode = StealthMode{ + .enabledTill = data.vactive_until_date().value_or_empty(), + .cooldownTill = data.vcooldown_until_date().value_or_empty(), + }; +} + void Stories::apply(not_null peer, const MTPUserStories *data) { if (!data) { applyDeletedFromSources(peer->id, StorySourcesList::NotHidden); @@ -536,6 +544,10 @@ void Stories::loadMore(StorySourcesList list) { }, [](const MTPDstories_allStoriesNotModified &) { }); + result.match([&](const auto &data) { + apply(data.vstealth_mode()); + }); + preloadListsMore(); }).fail([=] { _loadMoreRequestId[index] = 0; @@ -719,6 +731,7 @@ void Stories::applyDeleted(FullStoryId id) { } } if (_preloading && _preloading->id() == id) { + _preloading = nullptr; preloadFinished(id); } _owner->refreshStoryItemViews(id); @@ -836,6 +849,26 @@ std::shared_ptr Stories::lookupItem(not_null story) { return j->second.lock(); } +StealthMode Stories::stealthMode() const { + return _stealthMode.current(); +} + +rpl::producer Stories::stealthModeValue() const { + return _stealthMode.value(); +} + +void Stories::activateStealthMode(Fn done) { + const auto api = &session().api(); + using Flag = MTPstories_ActivateStealthMode::Flag; + api->request(MTPstories_ActivateStealthMode( + MTP_flags(Flag::f_past | Flag::f_future) + )).done([=](const MTPBool &result) { + if (done) done(); + }).fail([=] { + if (done) done(); + }).send(); +} + std::shared_ptr Stories::resolveItem(not_null story) { auto &items = _items[story->peer()->id]; auto i = items.find(story->id()); diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 48ea79688..545acdfe0 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -116,6 +116,14 @@ struct StoriesContext { friend inline bool operator==(StoriesContext, StoriesContext) = default; }; +struct StealthMode { + TimeId enabledTill = 0; + TimeId cooldownTill = 0; + + friend inline auto operator<=>(StealthMode, StealthMode) = default; + friend inline bool operator==(StealthMode, StealthMode) = default; +}; + inline constexpr auto kStorySourcesListCount = 2; class Stories final : public base::has_weak_ptr { @@ -139,6 +147,7 @@ public: void loadMore(StorySourcesList list); void apply(const MTPDupdateStory &data); void apply(const MTPDupdateReadStories &data); + void apply(const MTPStoriesStealthMode &stealthMode); void apply(not_null peer, const MTPUserStories *data); Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story); void loadAround(FullStoryId id, StoriesContext context); @@ -227,6 +236,10 @@ public: [[nodiscard]] std::shared_ptr lookupItem( not_null story); + [[nodiscard]] StealthMode stealthMode() const; + [[nodiscard]] rpl::producer stealthModeValue() const; + void activateStealthMode(Fn done = nullptr); + private: struct Saved { StoriesIds ids; @@ -375,6 +388,8 @@ private: base::Timer _pollingTimer; base::Timer _pollingViewsTimer; + rpl::variable _stealthMode; + }; } // namespace Data diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 237401606..2ee9496dd 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_recent_views.h" #include "media/stories/media_stories_reply.h" #include "media/stories/media_stories_share.h" +#include "media/stories/media_stories_stealth.h" #include "media/stories/media_stories_view.h" #include "media/audio/media_audio.h" #include "ui/boxes/confirm_box.h" @@ -1522,6 +1523,17 @@ void Controller::tryProcessKeyInput(not_null e) { _replyArea->tryProcessKeyInput(e); } +bool Controller::allowStealthMode() const { + const auto story = this->story(); + return story + && !story->peer()->isSelf() + && story->peer()->session().premiumPossible(); +} + +void Controller::setupStealthMode() { + SetupStealthMode(uiShow()); +} + rpl::lifetime &Controller::lifetime() { return _lifetime; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 35d82c9b0..a9703c423 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -167,6 +167,9 @@ public: [[nodiscard]] bool ignoreWindowMove(QPoint position) const; void tryProcessKeyInput(not_null e); + [[nodiscard]] bool allowStealthMode() const; + void setupStealthMode(); + [[nodiscard]] rpl::lifetime &lifetime(); private: diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp new file mode 100644 index 000000000..b5d441ee1 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp @@ -0,0 +1,407 @@ +/* +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 "media/stories/media_stories_stealth.h" + +#include "base/timer_rpl.h" +#include "base/unixtime.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "data/data_stories.h" +#include "info/profile/info_profile_icon.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_premium.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" +#include "ui/widgets/buttons.h" +#include "ui/painter.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "styles/style_media_view.h" +#include "styles/style_layers.h" + +namespace Media::Stories { +namespace { + +constexpr auto kAlreadyToastDuration = 4 * crl::time(1000); +constexpr auto kCooldownButtonLabelOpacity = 0.5; + +struct State { + Data::StealthMode mode; + TimeId now = 0; + bool premium = false; +}; + +struct Feature { + const style::icon &icon; + QString title; + TextWithEntities about; +}; + +[[nodiscard]] QString LeftText(int left) { + Expects(left >= 0); + + const auto hours = left / 3600; + const auto minutes = (left % 3600) / 60; + const auto seconds = left % 60; + const auto zero = QChar('0'); + if (hours) { + return u"%1:%2:%3"_q + .arg(hours) + .arg(minutes, 2, 10, zero) + .arg(seconds, 2, 10, zero); + } else if (minutes) { + return u"%1:%2"_q.arg(minutes).arg(seconds, 2, 10, zero); + } + return u"0:%1"_q.arg(left, 2, 10, zero); +} + +[[nodiscard]] Ui::Toast::Config ToastAlready(TimeId left) { + return { + .title = tr::lng_stealth_mode_already_title(tr::now), + .text = tr::lng_stealth_mode_already_about( + tr::now, + lt_left, + TextWithEntities{ LeftText(left) }, + Ui::Text::RichLangValue), + .st = &st::storiesStealthToast, + .duration = kAlreadyToastDuration, + .adaptive = true, + }; +} + +[[nodiscard]] Ui::Toast::Config ToastActivated() { + return { + .title = tr::lng_stealth_mode_enabled_tip_title(tr::now), + .text = tr::lng_stealth_mode_enabled_tip( + tr::now, + Ui::Text::RichLangValue), + .st = &st::storiesStealthToast, + .duration = kAlreadyToastDuration, + .adaptive = true, + }; +} + +[[nodiscard]] Ui::Toast::Config ToastCooldown() { + return { + .text = tr::lng_stealth_mode_cooldown_tip( + tr::now, + Ui::Text::RichLangValue), + .st = &st::storiesStealthToast, + .duration = kAlreadyToastDuration, + .adaptive = true, + }; +} + +[[nodiscard]] rpl::producer StateValue( + not_null session) { + return rpl::combine( + session->data().stories().stealthModeValue(), + Data::AmPremiumValue(session) + ) | rpl::map([](Data::StealthMode mode, bool premium) { + return rpl::make_producer([=](auto consumer) { + struct Info { + base::Timer timer; + bool firstSent = false; + bool enabledSent = false; + bool cooldownSent = false; + }; + auto lifetime = rpl::lifetime(); + const auto info = lifetime.make_state(); + const auto check = [=] { + auto send = !info->firstSent; + const auto now = base::unixtime::now(); + const auto left1 = (mode.enabledTill - now); + const auto left2 = (mode.cooldownTill - now); + info->firstSent = true; + if (!info->enabledSent && left1 <= 0) { + send = true; + info->enabledSent = true; + } + if (!info->cooldownSent && left2 <= 0) { + send = true; + info->cooldownSent = true; + } + const auto left = (left1 <= 0) + ? left2 + : (left2 <= 0) + ? left1 + : std::min(left1, left2); + if (left > 0) { + info->timer.callOnce(left * crl::time(1000)); + } + if (send) { + consumer.put_next(State{ mode, now, premium }); + } + if (left <= 0) { + consumer.put_done(); + } + }; + info->timer.setCallback(check); + check(); + return lifetime; + }); + }) | rpl::flatten_latest(); +} + +[[nodiscard]] Feature FeaturePast() { + return { + .icon = st::storiesStealthFeaturePastIcon, + .title = tr::lng_stealth_mode_past_title(tr::now), + .about = tr::lng_stealth_mode_past_about(tr::now), + }; +} + +[[nodiscard]] Feature FeatureNext() { + return { + .icon = st::storiesStealthFeatureNextIcon, + .title = tr::lng_stealth_mode_next_title(tr::now), + .about = tr::lng_stealth_mode_next_about(tr::now), + }; +} + +[[nodiscard]] object_ptr MakeLogo(QWidget *parent) { + const auto add = st::storiesStealthLogoAdd; + const auto icon = &st::storiesStealthLogoIcon; + const auto size = QSize(2 * add, 2 * add) + icon->size(); + auto result = object_ptr>( + parent, + object_ptr(parent), + st::storiesStealthLogoMargin); + const auto inner = result->entity(); + inner->resize(size); + inner->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(inner); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(st::storiesComposeBlue); + p.setPen(Qt::NoPen); + const auto left = (inner->width() - size.width()) / 2; + const auto top = (inner->height() - size.height()) / 2; + const auto rect = QRect(QPoint(left, top), size); + p.drawEllipse(rect); + icon->paintInCenter(p, rect); + }, inner->lifetime()); + return result; +} + +[[nodiscard]] object_ptr MakeTitle(QWidget *parent) { + return object_ptr>( + parent, + object_ptr( + parent, + tr::lng_stealth_mode_title(tr::now), + st::storiesStealthBox.title), + st::storiesStealthTitleMargin); +} + +[[nodiscard]] object_ptr MakeAbout( + QWidget *parent, + rpl::producer state) { + auto text = std::move(state) | rpl::map([](const State &state) { + return state.premium + ? tr::lng_stealth_mode_about(tr::now) + : tr::lng_stealth_mode_unlock_about(tr::now); + }); + return object_ptr>( + parent, + object_ptr( + parent, + std::move(text), + st::storiesStealthAbout), + st::storiesStealthAboutMargin); +} + +[[nodiscard]] object_ptr MakeFeature( + QWidget *parent, + Feature feature) { + auto result = object_ptr>( + parent, + object_ptr(parent), + st::storiesStealthFeatureMargin); + const auto widget = result->entity(); + const auto icon = Ui::CreateChild( + widget, + feature.icon, + st::storiesStealthFeatureIconPosition); + const auto title = Ui::CreateChild( + widget, + feature.title, + st::storiesStealthFeatureTitle); + const auto about = Ui::CreateChild( + widget, + rpl::single(feature.about), + st::storiesStealthFeatureAbout); + icon->show(); + title->show(); + about->show(); + widget->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto left = st::storiesStealthFeatureLabelLeft; + const auto available = width - left; + title->resizeToWidth(available); + about->resizeToWidth(available); + auto top = 0; + title->move(left, top); + top += title->height() + st::storiesStealthFeatureSkip; + about->move(left, top); + top += about->height(); + widget->resize(width, top); + }, widget->lifetime()); + return result; +} + +[[nodiscard]] object_ptr MakeButton( + QWidget *parent, + rpl::producer state) { + auto text = rpl::duplicate(state) | rpl::map([](const State &state) { + if (!state.premium) { + return tr::lng_stealth_mode_unlock(); + } else if (state.mode.cooldownTill <= state.now) { + return tr::lng_stealth_mode_enable(); + } + return rpl::single( + rpl::empty + ) | rpl::then( + base::timer_each(250) + ) | rpl::map([=] { + const auto now = base::unixtime::now(); + const auto left = std::max(state.mode.cooldownTill - now, 1); + return tr::lng_stealth_mode_cooldown_in( + tr::now, + lt_left, + LeftText(left)); + }) | rpl::type_erased(); + }) | rpl::flatten_latest(); + + auto result = object_ptr( + parent, + rpl::single(QString()), + st::storiesStealthBox.button); + const auto raw = result.data(); + + const auto label = Ui::CreateChild( + raw, + std::move(text), + st::storiesStealthButtonLabel); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + label->show(); + + const auto lock = Ui::CreateChild(raw); + lock->setAttribute(Qt::WA_TransparentForMouseEvents); + lock->resize(st::storiesStealthLockIcon.size()); + lock->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(lock); + st::storiesStealthLockIcon.paintInCenter(p, lock->rect()); + }, lock->lifetime()); + + const auto lockLeft = -st::storiesStealthButtonLabel.style.font->height; + const auto updateLabelLockGeometry = [=] { + const auto outer = raw->width(); + const auto added = -st::storiesStealthBox.button.width; + const auto skip = lock->isHidden() ? 0 : (lockLeft + lock->width()); + const auto width = outer - added - skip; + const auto top = st::storiesStealthBox.button.textTop; + label->resizeToWidth(width); + label->move(added / 2, top); + const auto inner = std::min(label->textMaxWidth(), width); + const auto right = (added / 2) + (width - inner) / 2 + inner; + const auto lockTop = (label->height() - lock->height()) / 2; + lock->move(right + lockLeft, top + lockTop); + }; + + std::move(state) | rpl::start_with_next([=](const State &state) { + const auto cooldown = state.premium + && (state.mode.cooldownTill > state.now); + label->setOpacity(cooldown ? kCooldownButtonLabelOpacity : 1.); + lock->setVisible(!state.premium); + updateLabelLockGeometry(); + }, label->lifetime()); + + raw->widthValue( + ) | rpl::start_with_next(updateLabelLockGeometry, label->lifetime()); + + return result; +} + +[[nodiscard]] object_ptr StealthModeBox( + std::shared_ptr show) { + return Box([=](not_null box) { + struct Data { + rpl::variable state; + bool requested = false; + }; + const auto data = box->lifetime().make_state(); + data->state = StateValue(&show->session()); + box->setWidth(st::boxWideWidth); + box->setStyle(st::storiesStealthBox); + box->addRow(MakeLogo(box)); + box->addRow(MakeTitle(box)); + box->addRow(MakeAbout(box, data->state.value())); + box->addRow(MakeFeature(box, FeaturePast())); + box->addRow( + MakeFeature(box, FeatureNext()), + (st::boxRowPadding + + QMargins(0, 0, 0, st::storiesStealthBoxBottom))); + box->setNoContentMargin(true); + box->addTopButton(st::storiesStealthBoxClose, [=] { + box->closeBox(); + }); + const auto button = box->addButton( + MakeButton(box, data->state.value())); + button->resizeToWidth(st::boxWideWidth + - st::storiesStealthBox.buttonPadding.left() + - st::storiesStealthBox.buttonPadding.right()); + button->setClickedCallback([=] { + const auto now = data->state.current(); + if (now.mode.enabledTill > now.now) { + show->showToast(ToastActivated()); + box->closeBox(); + } else if (!now.premium) { + data->requested = false; + const auto usage = ChatHelpers::WindowUsage::PremiumPromo; + if (const auto window = show->resolveWindow(usage)) { + Settings::ShowPremium( + window, + u"stories_stealth_mode"_q); + window->window().activate(); + } + } else if (now.mode.cooldownTill > now.now) { + show->showToast(ToastCooldown()); + box->closeBox(); + } else if (!data->requested) { + data->requested = true; + show->session().data().stories().activateStealthMode( + crl::guard(box, [=] { data->requested = false; })); + } + }); + data->state.value() | rpl::filter([](const State &state) { + return state.mode.enabledTill > state.now; + }) | rpl::start_with_next([=] { + box->closeBox(); + show->showToast(ToastActivated()); + }, box->lifetime()); + }); +} + +} // namespace + +void SetupStealthMode(std::shared_ptr show) { + const auto now = base::unixtime::now(); + const auto mode = show->session().data().stories().stealthMode(); + if (const auto left = mode.enabledTill - now; left > 0) { + show->showToast(ToastAlready(left)); + } else { + show->show(StealthModeBox(show)); + } +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.h b/Telegram/SourceFiles/media/stories/media_stories_stealth.h new file mode 100644 index 000000000..d4ccae4b6 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.h @@ -0,0 +1,18 @@ +/* +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 ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Media::Stories { + +void SetupStealthMode(std::shared_ptr show); + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index dfbab352b..c3cdbd01b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -111,6 +111,14 @@ void View::tryProcessKeyInput(not_null e) { _controller->tryProcessKeyInput(e); } +bool View::allowStealthMode() const { + return _controller->allowStealthMode(); +} + +void View::setupStealthMode() { + _controller->setupStealthMode(); +} + SiblingView View::sibling(SiblingType type) const { return _controller->sibling(type); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index 4d6cdac1e..b3dee4aa5 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -91,6 +91,9 @@ public: [[nodiscard]] bool ignoreWindowMove(QPoint position) const; void tryProcessKeyInput(not_null e); + [[nodiscard]] bool allowStealthMode() const; + void setupStealthMode(); + [[nodiscard]] rpl::lifetime &lifetime(); private: diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index adf770688..e291abe45 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -413,8 +413,8 @@ storiesSiblingWidthMin: 200px; // Try making sibling not less than this. storiesMaxNameFontSize: 17px; storiesRadius: 8px; storiesControlSize: 64px; -storiesLeft: icon {{ "mediaview/stories_next-flip_horizontal", mediaviewControlFg }}; -storiesRight: icon {{ "mediaview/stories_next", mediaviewControlFg }}; +storiesLeft: icon {{ "stories/next-flip_horizontal", mediaviewControlFg }}; +storiesRight: icon {{ "stories/next", mediaviewControlFg }}; storiesSliderWidth: 2px; storiesSliderMargin: margins(8px, 7px, 8px, 6px); storiesSliderSkip: 4px; @@ -911,3 +911,73 @@ storiesInfoTooltipMaxWidth: 360px; storiesCaptionPullThreshold: 50px; storiesShowMorePadding: margins(6px, 4px, 6px, 4px); storiesShowMoreFont: semiboldFont; + +storiesStealthLogoIcon: icon{{ "stories/stealth_logo", storiesComposeWhiteText }}; +storiesStealthLogoAdd: 12px; +storiesStealthLogoMargin: margins(0px, 28px, 0px, 7px); +storiesStealthBox: Box(defaultBox) { + buttonPadding: margins(10px, 10px, 10px, 10px); + buttonHeight: 42px; + button: RoundButton(defaultBoxButton) { + height: 42px; + textTop: 12px; + font: font(13px semibold); + + textFg: storiesComposeWhiteText; + textFgOver: storiesComposeWhiteText; + numbersTextFg: storiesComposeWhiteText; + numbersTextFgOver: storiesComposeWhiteText; + textBg: storiesComposeBlue; + textBgOver: storiesComposeBlue; + + ripple: universalRippleAnimation; + } + margin: margins(0px, 56px, 0px, 10px); + bg: groupCallMembersBg; + title: FlatLabel(boxTitle) { + textFg: groupCallMembersFg; + align: align(top); + } + titleAdditionalFg: groupCallMemberNotJoinedStatus; +} +storiesStealthButtonLabel: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; + textFg: storiesComposeWhiteText; + align: align(top); + minWidth: 20px; + maxHeight: 20px; +} +storiesStealthLockIcon: icon {{ "dialogs/dialogs_lock_on", storiesComposeWhiteText }}; +storiesStealthTitleMargin: margins(0px, 10px, 0px, 0px); +storiesStealthBoxClose: IconButton(defaultIconButton) { + width: boxTitleHeight; + height: boxTitleHeight; + + icon: icon {{ "box_button_close", storiesComposeGrayIcon }}; + iconOver: icon {{ "box_button_close", storiesComposeGrayIcon }}; + + rippleAreaPosition: point(4px, 4px); + rippleAreaSize: 40px; + ripple: storiesComposeRippleLight; +} +storiesStealthAbout: FlatLabel(defaultFlatLabel) { + textFg: storiesComposeGrayText; + align: align(top); + minWidth: 20px; +} +storiesStealthAboutMargin: margins(0px, 5px, 0px, 15px); +storiesStealthFeatureTitle: storiesHeaderName; +storiesStealthFeatureAbout: FlatLabel(defaultFlatLabel) { + textFg: storiesComposeGrayText; + minWidth: 20px; +} +storiesStealthFeaturePastIcon: icon{{ "stories/stealth_5m", storiesComposeBlue }}; +storiesStealthFeatureNextIcon: icon{{ "stories/stealth_25m", storiesComposeBlue }}; +storiesStealthFeatureIconPosition: point(3px, 7px); +storiesStealthFeatureMargin: margins(0px, 8px, 0px, 6px); +storiesStealthFeatureLabelLeft: 46px; +storiesStealthFeatureSkip: 2px; +storiesStealthBoxBottom: 11px; +storiesStealthToast: Toast(defaultMultilineToast) { + maxWidth: 340px; +} diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index fb30c3b4d..ee6b3bd53 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1631,6 +1631,15 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { } }, &st::mediaMenuIconReport); }(); + if (_stories && _stories->allowStealthMode()) { + const auto now = base::unixtime::now(); + const auto stealth = _session->data().stories().stealthMode(); + addAction(tr::lng_stealth_mode_menu_item(tr::now), [=] { + _stories->setupStealthMode(); + }, ((_session->premium() || (stealth.enabledTill > now)) + ? &st::mediaMenuIconStealth + : &st::mediaMenuIconStealthLocked)); + } if (story && story->canReport()) { addAction(tr::lng_profile_report(tr::now), [=] { _stories->reportRequested(); @@ -5249,6 +5258,7 @@ void OverlayWidget::setContext( { story->peer->id, story->id }); if (maybeStory) { _stories->show(*maybeStory, story->within); + _dropdown->raise(); } } else { _message = nullptr; @@ -5289,7 +5299,6 @@ void OverlayWidget::setStoriesPeer(PeerData *peer) { updateControlsGeometry(); }, _stories->lifetime()); _storiesChanged.fire({}); - _dropdown->raise(); } } diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index f1b1a2029..dfe5430b2 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -134,6 +134,8 @@ mediaMenuIconProfile: icon {{ "menu/profile", mediaviewMenuFg }}; mediaMenuIconReport: icon {{ "menu/report", mediaviewMenuFg }}; mediaMenuIconSaveStory: icon {{ "menu/stories_save", mediaviewMenuFg }}; mediaMenuIconArchiveStory: icon {{ "menu/stories_archive", mediaviewMenuFg }}; +mediaMenuIconStealthLocked: icon {{ "menu/stealth_locked", mediaviewMenuFg }}; +mediaMenuIconStealth: icon {{ "menu/stealth", mediaviewMenuFg }}; menuIconDeleteAttention: icon {{ "menu/delete", menuIconAttentionColor }}; menuIconLeaveAttention: icon {{ "menu/leave", menuIconAttentionColor }}; From 3adb0c1856d6d16566da0501bc1c9ed96501b007 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Aug 2023 11:57:35 +0200 Subject: [PATCH 009/104] Show correct reply placeholder in stealth mode. --- .../media/stories/media_stories_reply.cpp | 34 +++++++++++++++- .../media/stories/media_stories_stealth.cpp | 40 +++++++++---------- .../media/stories/media_stories_stealth.h | 2 + .../media/view/media_view_overlay_widget.cpp | 2 +- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 7ea8ce8b7..d6fbfd7c6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_sending.h" #include "apiwrap.h" #include "base/call_delayed.h" +#include "base/timer_rpl.h" +#include "base/unixtime.h" #include "boxes/premium_limits_box.h" #include "boxes/send_files_box.h" #include "chat_helpers/compose/compose_show.h" @@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" +#include "media/stories/media_stories_stealth.h" #include "menu/menu_send.h" #include "storage/localimageloader.h" #include "storage/storage_account.h" @@ -42,6 +45,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_media_view.h" namespace Media::Stories { +namespace { + +[[nodiscard]] rpl::producer PlaceholderText( + const std::shared_ptr &show) { + return show->session().data().stories().stealthModeValue( + ) | rpl::map([](Data::StealthMode value) { + return value.enabledTill; + }) | rpl::distinct_until_changed() | rpl::map([](TimeId till) { + return rpl::single( + rpl::empty + ) | rpl::then( + base::timer_each(250) + ) | rpl::map([=] { + return till - base::unixtime::now(); + }) | rpl::take_while([](TimeId left) { + return left > 0; + }) | rpl::then( + rpl::single(0) + ) | rpl::map([](TimeId left) { + return left + ? tr::lng_stealth_mode_countdown( + lt_left, + rpl::single(TimeLeftText(left))) + : tr::lng_story_reply_ph(); + }) | rpl::flatten_latest(); + }) | rpl::flatten_latest(); +} + +} // namespace class ReplyArea::Cant final : public Ui::RpWidget { public: @@ -85,7 +117,7 @@ ReplyArea::ReplyArea(not_null controller) .mode = HistoryView::ComposeControlsMode::Normal, .sendMenuType = SendMenu::Type::SilentOnly, .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), - .customPlaceholder = tr::lng_story_reply_ph(), + .customPlaceholder = PlaceholderText(_controller->uiShow()), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp index b5d441ee1..7bf25a1cf 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp @@ -45,31 +45,13 @@ struct Feature { TextWithEntities about; }; -[[nodiscard]] QString LeftText(int left) { - Expects(left >= 0); - - const auto hours = left / 3600; - const auto minutes = (left % 3600) / 60; - const auto seconds = left % 60; - const auto zero = QChar('0'); - if (hours) { - return u"%1:%2:%3"_q - .arg(hours) - .arg(minutes, 2, 10, zero) - .arg(seconds, 2, 10, zero); - } else if (minutes) { - return u"%1:%2"_q.arg(minutes).arg(seconds, 2, 10, zero); - } - return u"0:%1"_q.arg(left, 2, 10, zero); -} - [[nodiscard]] Ui::Toast::Config ToastAlready(TimeId left) { return { .title = tr::lng_stealth_mode_already_title(tr::now), .text = tr::lng_stealth_mode_already_about( tr::now, lt_left, - TextWithEntities{ LeftText(left) }, + TextWithEntities{ TimeLeftText(left) }, Ui::Text::RichLangValue), .st = &st::storiesStealthToast, .duration = kAlreadyToastDuration, @@ -277,7 +259,7 @@ struct Feature { return tr::lng_stealth_mode_cooldown_in( tr::now, lt_left, - LeftText(left)); + TimeLeftText(left)); }) | rpl::type_erased(); }) | rpl::flatten_latest(); @@ -404,4 +386,22 @@ void SetupStealthMode(std::shared_ptr show) { } } +QString TimeLeftText(int left) { + Expects(left >= 0); + + const auto hours = left / 3600; + const auto minutes = (left % 3600) / 60; + const auto seconds = left % 60; + const auto zero = QChar('0'); + if (hours) { + return u"%1:%2:%3"_q + .arg(hours) + .arg(minutes, 2, 10, zero) + .arg(seconds, 2, 10, zero); + } else if (minutes) { + return u"%1:%2"_q.arg(minutes).arg(seconds, 2, 10, zero); + } + return u"0:%1"_q.arg(left, 2, 10, zero); +} + } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.h b/Telegram/SourceFiles/media/stories/media_stories_stealth.h index d4ccae4b6..f40b77bc4 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_stealth.h +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.h @@ -15,4 +15,6 @@ namespace Media::Stories { void SetupStealthMode(std::shared_ptr show); +[[nodiscard]] QString TimeLeftText(int left); + } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index ee6b3bd53..0cd3ca35e 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1313,7 +1313,7 @@ void OverlayWidget::updateControls() { } _shareNav = navRect(index); _shareNavOver = style::centerrect(_shareNav, overRect); - _shareNavIcon = style::centerrect(_shareNav, st::mediaviewSave); + _shareNavIcon = style::centerrect(_shareNav, st::mediaviewShare); if (_shareVisible) { ++index; } From 4bd925ac2c730bca750fdf03dd0d1a0eee0c1e1c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Aug 2023 21:41:58 +0200 Subject: [PATCH 010/104] Implement simple UI for single-type likes in stories. --- Telegram/Resources/icons/chat/input_like.png | Bin 0 -> 699 bytes .../Resources/icons/chat/input_like@2x.png | Bin 0 -> 1356 bytes .../Resources/icons/chat/input_like@3x.png | Bin 0 -> 2003 bytes Telegram/Resources/icons/chat/input_liked.png | Bin 0 -> 559 bytes .../Resources/icons/chat/input_liked@2x.png | Bin 0 -> 942 bytes .../Resources/icons/chat/input_liked@3x.png | Bin 0 -> 1362 bytes Telegram/Resources/langs/lang.strings | 2 + .../chat_helpers/chat_helpers.style | 2 + .../chat_helpers/compose/compose_features.h | 1 + Telegram/SourceFiles/data/data_story.cpp | 9 +- Telegram/SourceFiles/data/data_story.h | 3 +- .../view/controls/compose_controls_common.h | 1 + .../history_view_compose_controls.cpp | 49 +++++++- .../controls/history_view_compose_controls.h | 7 ++ .../stories/media_stories_controller.cpp | 20 +++- .../media/stories/media_stories_controller.h | 7 +- .../media/stories/media_stories_reply.cpp | 11 ++ .../media/stories/media_stories_reply.h | 2 + .../media/stories/media_stories_stealth.cpp | 2 +- .../SourceFiles/media/view/media_view.style | 7 ++ .../media/view/media_view_overlay_opengl.cpp | 45 +++++--- .../media/view/media_view_overlay_opengl.h | 3 +- .../media/view/media_view_overlay_widget.cpp | 107 +++++++++++++----- .../media/view/media_view_overlay_widget.h | 13 ++- .../ui/effects/emoji_fly_animation.cpp | 3 +- .../ui/effects/emoji_fly_animation.h | 4 +- Telegram/SourceFiles/ui/menu_icons.style | 1 + 27 files changed, 235 insertions(+), 64 deletions(-) create mode 100644 Telegram/Resources/icons/chat/input_like.png create mode 100644 Telegram/Resources/icons/chat/input_like@2x.png create mode 100644 Telegram/Resources/icons/chat/input_like@3x.png create mode 100644 Telegram/Resources/icons/chat/input_liked.png create mode 100644 Telegram/Resources/icons/chat/input_liked@2x.png create mode 100644 Telegram/Resources/icons/chat/input_liked@3x.png diff --git a/Telegram/Resources/icons/chat/input_like.png b/Telegram/Resources/icons/chat/input_like.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ccead7e2d8a4b30fce9a3dcb2d7f254b984943 GIT binary patch literal 699 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/input_liked.png b/Telegram/Resources/icons/chat/input_liked.png new file mode 100644 index 0000000000000000000000000000000000000000..5a267ca1dcc25a971c766b452bb6fcd064def113 GIT binary patch literal 559 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY^JA+V~B;| z*(tW`njA!0ze;j(aZf!Ja7#+KV=5;b@4F`57i=+Hs-2>1bq#m%NOw-{VwJeq@XJr; zX}Utk+{qd5=2o9Oo|-lz=;d{{nOPmrD?KNzy85bWubJ=Xy8Z17RJ$Jl1$87e;ikP~-lPO7&*LHtu;7N)Q;!xo^ye4e51?#-qZ`=Og zEN;o1vo%I9(PzW5%b6<{FkM*5euUM)O(7uncHi{w;|6ZNor@yw%nUo!x9s1C!+x%B zYvoO6pA|n*IOqM_iOKu#@1In)ci!eXFUvd6C!|~Ub+53f-uM2vsOuvIj%S}MT^vnh z__{4FOSuLLDX3VKKVeyRb*6E~aRZ)NO?$#tKU8+GyzJ)d#8$ZY;t7VtRd@5|`z`OU zZDfjEe);6G2{-HZ*YAxhj?jqJ6OUo86bU-~%W>Avz8Oc7-4%qC-eNI=J6lD0=H^m=+yz%ziX8|u(Pn-1U$LdvG)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NF7)eAyR9Fe^SkFshQ53FmRPqNQ zW>o%Y5xLN%DQz%$8)~V^RD;3%b!W`{?!h!^6Xag9D4jqSNVKUS68b zW~o$)#bOT+4~*Q+&CNg{;B-2-wzjld?bFj!yWLKuQpsep-|sULsUeL<6AFc1UtfQa zuF+_ePN!Wi7eC+ac4xC$Qqm^}1y4^;p*TO0#aOM@R;xuTit}e@XV8FgEdY^71dq}% z6bA{G13b2~v(xMKXtnr!yb?0w0tpmkVDc0u)n`AhAO)zh!U)fsdzljP8K5yCkWZl>gwvp$A=h_?3y47 z(#X>{guGs_TrNjT$pDCYJRT&+8QR|7My$jDKa>6mLwGMZLlSodms2X0XvrBG3gYjbhROIEM@d!}Ig= zSdQxu1TvY7G$wEr5Jhfq{uA%+?y^r+F``FDN0UDA_2uQI7^<|I)oOKdk=r)7lizr6 zZ;zHN2JiFv2qq?|zkCgy-QW57`Ghoi3DZTV)0th5U12yJZftCj9q?JWaWGe~E6x}V z27`QE7ISiPGNU9TJsb|Rw23j;{35+y!>lNi$%vwVAXO?=p-^CaQ2`;CdZ(TeahtHg z;Xxxnn3z(l)nGQq>kGkaYioCRcSM1y$lKdnqtPhD0YArVHs9ahPbEL`VMsTbO#I5i z8Tj`@aG$^pqzl)kWkSQJn%>7vYBU;O+#B2+5)hugCB+DVNI( z6DV|jeT`3mUvC9fTwY$z=krLI2(GTK;K)EJq60WHFD@>KR)Eaqa_C7^dL; zf$u4iNGvTap^r%o{C+>&9T=?M-{0Y}aD03$wF9Ew_g@z=G$-dBkh%ju0gw9pbUZyc QRR91007*qoM6N<$f^i3!+5i9m literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/input_liked@3x.png b/Telegram/Resources/icons/chat/input_liked@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5d6c546e61902de675c4cd4131b4e8f09e0ed329 GIT binary patch literal 1362 zcmV-Y1+DstP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISe5TT3W)Q560?Q%wGW zJO&;kqLgHaGQmV5N>Y^a2r-kCM9EB2$}>tx-<`xhT@cZB6 zx3_m#SXgLi=+o2FpMTHk>FN9X`=^4V3VwcmSy@@Vy}f5= zXCGgmmzS6Q{r&d#_N1gFcXxMw1Rfq9$;rtb9UTV;2XAk0U#)&zUS9V1_vhr~z%{>3 z0ZmU&&(hM;i;D~5SW^V;y1F`FUthKZV6CsO2Xsv%q!ManWo3ZDwkg(%LD<>ZA)f4t z=jZ2u;^pO~c81FG@^a2FDIM+Z?j|NCstwB2U>ab?E3FHaS5{U+LPC^|A+D~j3K>x< z159>yw$h+X86j8Cd!-wm^7Zv~YHF&@eOg-D&CLx@FNO{v0hkOQTc4_`Du#8TCcN2- zi;GK2N>C8$$WR768E!E#G0)G>47;LDprj)!c*mRsNHK2saj&nhi$XVEA0RbfPGIYy z$+)4R0q+dWCYP!V3=G6pVR3O$jY?zZf$ap=d+aCF+Adi0`1lwc9E@EG!KSfA$ji$! z3oDQTQ~)Ndv_(_=x|F}?wY4>ECd%5lxHx4Wvy=f_$pU3M6|sO44O56}*pyg&e7q@` z5RhuvlvreBBU2^T^i7FVPSy*p4Ai-6m)lYX8=SA7Y`2);o;#d^z63W+*}E-26U;c ztYjm%-`dmDV*pSg7gJMHH~_W}ovne3$&HN-A;%6?#*Hq{s@a%%TXEaXd7{4A_Eryt1yRorRN1#HcT3TB8 zamr|Rc6JH@(nT3gWv~kM!N(~>Lqod2&p~1_sD&kUEY=*C3B* zYilDwg~YC|E`!!9iP$tZHw%&ZRBml;)gi@_K+NBzwzl>YE@z>ssfpjdGc_wJD)>Ki ztEs7RCRS131qB88sGB`vAde7;T6VsEW@aW5-tV~K);K>u-}xxz5JX2u&(F`Rxh*X% z>73!K@csC#M`cl65fv4cl9Gad3gEqWbaaG+PMi*@)vOvT0#*d92v`xYA|M=rzw!y> Uh($ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e6dfeeb0a..70306ad8f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -272,6 +272,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_noforwards_channel" = "Sorry, forwarding from this channel is disabled by admins."; "lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins."; "lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins."; +"lng_error_nocopy_story" = "Sorry, copying of this story is disabled by the author."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; @@ -3876,6 +3877,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_done" = "This story is hidden from your profile."; "lng_stories_archive_done_many#one" = "{count} story is hidden from your profile."; "lng_stories_archive_done_many#other" = "{count} stories are hidden from your profile."; +"lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk."; "lng_stealth_mode_menu_item" = "Stealth Mode"; "lng_stealth_mode_title" = "Stealth Mode"; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 79c85a03d..75fbfbbb0 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -196,6 +196,8 @@ ComposeControls { send: SendButton; attach: IconButton; emoji: EmojiButton; + like: IconButton; + liked: icon; suggestions: EmojiSuggestions; tabbed: EmojiPan; tabbedHeightMin: pixels; diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h index 2f9915679..ba6f43b4e 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace ChatHelpers { struct ComposeFeatures { + bool likes = false; bool sendAs = true; bool ttlInfo = true; bool botCommandSend = true; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index fbc678581..e8389ad2c 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -276,8 +276,13 @@ bool Story::edited() const { return _edited; } -bool Story::canDownload() const { - return /*!forbidsForward() || */_peer->isSelf(); +bool Story::canDownloadIfPremium() const { + return !forbidsForward() || _peer->isSelf(); +} + +bool Story::canDownloadChecked() const { + return _peer->isSelf() + || (canDownloadIfPremium() && _peer->session().premium()); } bool Story::canShare() const { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 8ea17b7b7..70370f176 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -100,7 +100,8 @@ public: [[nodiscard]] bool forbidsForward() const; [[nodiscard]] bool edited() const; - [[nodiscard]] bool canDownload() const; + [[nodiscard]] bool canDownloadIfPremium() const; + [[nodiscard]] bool canDownloadChecked() const; [[nodiscard]] bool canShare() const; [[nodiscard]] bool canDelete() const; [[nodiscard]] bool canReport() const; diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h index 06f295ee8..8af04e830 100644 --- a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -41,6 +41,7 @@ struct SetHistoryArgs { Fn sendActionFactory; rpl::producer slowmodeSecondsLeft; rpl::producer sendDisabledBySlowmode; + rpl::producer liked; rpl::producer> writeRestriction; }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 4901c3bf9..6990d5b22 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1069,9 +1069,10 @@ ComposeControls::ComposeControls( , _wrap(std::make_unique(parent)) , _writeRestricted(std::make_unique(parent)) , _send(std::make_shared(_wrap.get(), _st.send)) -, _attachToggle(Ui::CreateChild( - _wrap.get(), - _st.attach)) +, _like(_features.likes + ? Ui::CreateChild(_wrap.get(), _st.like) + : nullptr) +, _attachToggle(Ui::CreateChild(_wrap.get(), _st.attach)) , _tabbedSelectorToggle(Ui::CreateChild( _wrap.get(), _st.emoji)) @@ -1138,6 +1139,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { | rpl::then(std::move(args.slowmodeSecondsLeft)); _sendDisabledBySlowmode = rpl::single(false) | rpl::then(std::move(args.sendDisabledBySlowmode)); + _liked = args.liked ? std::move(args.liked) : rpl::single(false); _writeRestriction = rpl::single(std::optional()) | rpl::then(std::move(args.writeRestriction)); const auto history = *args.history; @@ -1153,6 +1155,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { initWebpageProcess(); initForwardProcess(); updateBotCommandShown(); + updateLikeShown(); updateMessagesTTLShown(); updateControlsGeometry(_wrap->size()); updateControlsVisibility(); @@ -1559,6 +1562,15 @@ void ComposeControls::init() { _botCommandStart->setClickedCallback([=] { setText({ "/" }); }); } + if (_like) { + _like->setClickedCallback([=] { _likeToggled.fire({}); }); + _liked.value( + ) | rpl::start_with_next([=](bool liked) { + const auto icon = liked ? &_st.liked : nullptr; + _like->setIconOverride(icon, icon); + }, _like->lifetime()); + } + _wrap->sizeValue( ) | rpl::start_with_next([=](QSize size) { updateControlsGeometry(size); @@ -1980,7 +1992,7 @@ void ComposeControls::fieldChanged() { if (!_hasSendText.current() && _preview) { _preview->setState(Data::PreviewState::Allowed); } - if (updateBotCommandShown()) { + if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); updateControlsGeometry(_wrap->size()); } @@ -2521,6 +2533,7 @@ void ComposeControls::updateControlsGeometry(QSize size) { - st::historySendRight - _send->width() - _tabbedSelectorToggle->width() + - (_likeShown ? _like->width() : 0) - (_botCommandShown ? _botCommandStart->width() : 0) - (_silent ? _silent->width() : 0) - (_ttlInfo ? _ttlInfo->width() : 0); @@ -2560,6 +2573,12 @@ void ComposeControls::updateControlsGeometry(QSize size) { right += _send->width(); _tabbedSelectorToggle->moveToRight(right, buttonsTop); right += _tabbedSelectorToggle->width(); + if (_like) { + _like->moveToRight(right, buttonsTop); + if (_likeShown) { + right += _like->width(); + } + } if (_botCommandStart) { _botCommandStart->moveToRight(right, buttonsTop); if (_botCommandShown) { @@ -2584,6 +2603,9 @@ void ComposeControls::updateControlsVisibility() { if (_botCommandStart) { _botCommandStart->setVisible(_botCommandShown); } + if (_like) { + _like->setVisible(_likeShown); + } if (_ttlInfo) { _ttlInfo->show(); } @@ -2598,6 +2620,15 @@ void ComposeControls::updateControlsVisibility() { } } +bool ComposeControls::updateLikeShown() { + auto shown = _like && !HasSendText(_field); + if (_likeShown != shown) { + _likeShown = shown; + return true; + } + return false; +} + bool ComposeControls::updateBotCommandShown() { auto shown = false; const auto peer = _history ? _history->peer.get() : nullptr; @@ -3100,6 +3131,10 @@ rpl::producer> ComposeControls::viewportEvents() const { return _voiceRecordBar->lockViewportEvents(); } +rpl::producer<> ComposeControls::likeToggled() const { + return _likeToggled.events(); +} + bool ComposeControls::isRecording() const { return _voiceRecordBar->isRecording(); } @@ -3123,6 +3158,12 @@ rpl::producer ComposeControls::fieldMenuShownValue() const { return _field->menuShownValue(); } +not_null ComposeControls::likeAnimationTarget() const { + Expects(_like != nullptr); + + return _like; +} + bool ComposeControls::preventsClose(Fn &&continueCallback) const { if (_voiceRecordBar->isActive()) { _voiceRecordBar->showDiscardBox(std::move(continueCallback)); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index de7002012..80ad5e7b2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -161,6 +161,7 @@ public: [[nodiscard]] rpl::producer inlineResultChosen() const; [[nodiscard]] rpl::producer sendActionUpdates() const; [[nodiscard]] rpl::producer> viewportEvents() const; + [[nodiscard]] rpl::producer<> likeToggled() const; [[nodiscard]] auto scrollKeyEvents() const -> rpl::producer>; [[nodiscard]] auto editLastMessageRequests() const @@ -221,6 +222,7 @@ public: [[nodiscard]] rpl::producer recordingActiveValue() const; [[nodiscard]] rpl::producer hasSendTextValue() const; [[nodiscard]] rpl::producer fieldMenuShownValue() const; + [[nodiscard]] not_null likeAnimationTarget() const; void applyCloudDraft(); void applyDraft( @@ -292,6 +294,7 @@ private: bool showRecordButton() const; void drawRestrictedWrite(QPainter &p, const QString &error); bool updateBotCommandShown(); + bool updateLikeShown(); void cancelInlineBot(); void clearInlineBot(); @@ -344,6 +347,7 @@ private: Fn _sendActionFactory; rpl::variable _slowmodeSecondsLeft; rpl::variable _sendDisabledBySlowmode; + rpl::variable _liked; rpl::variable> _writeRestriction; rpl::variable _hidden; Mode _mode = Mode::Normal; @@ -354,6 +358,7 @@ private: std::optional _backgroundRect; const std::shared_ptr _send; + Ui::IconButton * const _like = nullptr; const not_null _attachToggle; std::unique_ptr _replaceMedia; const not_null _tabbedSelectorToggle; @@ -386,6 +391,7 @@ private: rpl::event_stream> _scrollKeyEvents; rpl::event_stream> _editLastMessageRequests; rpl::event_stream> _attachRequests; + rpl::event_stream<> _likeToggled; rpl::event_stream _replyNextRequests; rpl::event_stream<> _focusRequests; rpl::variable _recording; @@ -407,6 +413,7 @@ private: mtpRequestId _inlineBotResolveRequestId = 0; bool _isInlineBot = false; bool _botCommandShown = false; + bool _likeShown = false; FullMsgId _editingId; std::shared_ptr _photoEditMedia; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 2ee9496dd..32eae7905 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -313,7 +313,7 @@ Controller::Controller(not_null delegate) .type = Ui::MessageSendingAnimationFrom::Type::Emoji, .globalStartGeometry = id.globalGeometry, .frame = id.icon, - }); + }, _wrap.get()); _replyArea->sendReaction(id.id); unfocusReply(); }, _lifetime); @@ -587,6 +587,18 @@ bool Controller::skipCaption() const { return _captionFullView != nullptr; } +bool Controller::liked() const { + return _liked.current(); +} + +rpl::producer Controller::likedValue() const { + return _liked.value(); +} + +void Controller::toggleLiked(bool liked) { + _liked = liked; +} + void Controller::showFullCaption() { if (_captionText.empty()) { return; @@ -892,6 +904,7 @@ bool Controller::changeShown(Data::Story *story) { story, Data::Stories::Polling::Viewer); } + _liked = false; return true; } @@ -1551,7 +1564,8 @@ void Controller::updatePowerSaveBlocker(const Player::TrackState &state) { void Controller::startReactionAnimation( Data::ReactionId id, - Ui::MessageSendingAnimationFrom from) { + Ui::MessageSendingAnimationFrom from, + not_null target) { Expects(shown()); auto args = Ui::ReactionFlyAnimationArgs{ @@ -1568,7 +1582,7 @@ void Controller::startReactionAnimation( Data::CustomEmojiSizeTag::Isolated); const auto layer = _reactionAnimation->layer(); _wrap->paintRequest() | rpl::start_with_next([=] { - if (!_reactionAnimation->paintBadgeFrame(_wrap.get())) { + if (!_reactionAnimation->paintBadgeFrame(target)) { InvokeQueued(layer, [=] { _reactionAnimation = nullptr; _wrap->update(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index a9703c423..115e7e80a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -123,6 +123,9 @@ public: [[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] bool skipCaption() const; + [[nodiscard]] bool liked() const; + [[nodiscard]] rpl::producer likedValue() const; + void toggleLiked(bool liked); void showFullCaption(); void captionClosing(); void captionClosed(); @@ -236,7 +239,8 @@ private: void startReactionAnimation( Data::ReactionId id, - Ui::MessageSendingAnimationFrom from); + Ui::MessageSendingAnimationFrom from, + not_null target); const not_null _delegate; @@ -269,6 +273,7 @@ private: Data::StoriesContext _context; std::optional _source; std::optional _list; + rpl::variable _liked; FullStoryId _waitingForId; int _waitingForDelta = 0; int _index = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index d6fbfd7c6..53a5c3435 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -121,6 +121,7 @@ ReplyArea::ReplyArea(not_null controller) .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { + .likes = true, .sendAs = false, .ttlInfo = false, .botCommandSend = false, @@ -620,6 +621,11 @@ void ReplyArea::initActions() { sendInlineResult(chosen.result, chosen.bot, chosen.options, localId); }, _lifetime); + _controls->likeToggled( + ) | rpl::start_with_next([=] { + _controller->toggleLiked(!_controller->liked()); + }, _lifetime); + _controls->setMimeDataHook([=]( not_null data, Ui::InputField::MimeAction action) { @@ -660,6 +666,7 @@ void ReplyArea::show(ReplyAreaData data) { const auto history = user ? user->owner().history(user).get() : nullptr; _controls->setHistory({ .history = history, + .liked = _controller->likedValue(), }); _controls->clear(); const auto hidden = user && user->isSelf(); @@ -718,6 +725,10 @@ void ReplyArea::tryProcessKeyInput(not_null e) { _controls->tryProcessKeyInput(e); } +not_null ReplyArea::likeAnimationTarget() const { + return _controls->likeAnimationTarget(); +} + void ReplyArea::showPremiumToast(not_null emoji) { // #TODO stories } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index 9b9662b4a..c3c13abdf 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -70,6 +70,8 @@ public: [[nodiscard]] bool ignoreWindowMove(QPoint position) const; void tryProcessKeyInput(not_null e); + [[nodiscard]] not_null likeAnimationTarget() const; + private: class Cant; diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp index 7bf25a1cf..8bacca224 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp @@ -353,7 +353,7 @@ struct Feature { if (const auto window = show->resolveWindow(usage)) { Settings::ShowPremium( window, - u"stories_stealth_mode"_q); + u"stories__stealth_mode"_q); window->window().activate(); } } else if (now.mode.cooldownTill > now.now) { diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index e291abe45..5eb311f76 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -109,6 +109,7 @@ mediaviewRight: icon { { "mediaview/next", mediaviewControlFg } }; mediaviewSave: icon {{ "mediaview/download", mediaviewControlFg }}; +mediaviewSaveLocked: icon {{ "mediaview/download_locked", mediaviewControlFg }}; mediaviewShare: icon {{ "mediaview/viewer_share", mediaviewControlFg }}; mediaviewRotate: icon {{ "mediaview/rotate", mediaviewControlFg }}; mediaviewMore: icon {{ "title_menu_dots", mediaviewControlFg }}; @@ -466,6 +467,10 @@ storiesAttach: IconButton(historyAttach) { iconOver: icon {{ "chat/input_attach", storiesComposeGrayIcon }}; ripple: storiesComposeRippleLight; } +storiesLike: IconButton(storiesAttach) { + icon: icon {{ "chat/input_like", storiesComposeGrayIcon }}; + iconOver: icon {{ "chat/input_like", storiesComposeGrayIcon }}; +} storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRemoveSet: IconButton(stickerPanRemoveSet) { @@ -676,6 +681,8 @@ storiesComposeControls: ComposeControls(defaultComposeControls) { } attach: storiesAttach; emoji: storiesAttachEmoji; + like: storiesLike; + liked: icon{{ "chat/input_liked", settingsIconBg1 }}; suggestions: EmojiSuggestions(defaultEmojiSuggestions) { dropdown: InnerDropdown(emojiSuggestionsDropdown) { animation: PanelAnimation(defaultPanelAnimation) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp index 7851ed7f9..570eed0af 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/view/media_view_overlay_opengl.h" +#include "data/data_peer_values.h" // AmPremiumValue. #include "ui/gl/gl_shader.h" #include "ui/painter.h" #include "media/stories/media_stories_view.h" @@ -122,7 +123,16 @@ OverlayWidget::RendererGL::RendererGL(not_null owner) crl::on_main(this, [=] { _owner->_storiesChanged.events( ) | rpl::start_with_next([=] { - invalidateControls(); + if (_owner->_storiesSession) { + Data::AmPremiumValue( + _owner->_storiesSession + ) | rpl::start_with_next([=] { + invalidateControls(); + }, _storiesLifetime); + } else { + _storiesLifetime.destroy(); + invalidateControls(); + } }, _lifetime); }); } @@ -648,8 +658,7 @@ void OverlayWidget::RendererGL::paintControl( QRect inner, float64 innerOpacity, const style::icon &icon) { - const auto stories = (_owner->_stories != nullptr); - const auto meta = ControlMeta(control, stories); + const auto meta = controlMeta(control); Assert(meta.icon == &icon); const auto overAlpha = overOpacity * kOverBackgroundOpacity; @@ -707,18 +716,25 @@ void OverlayWidget::RendererGL::paintControl( FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset); } -auto OverlayWidget::RendererGL::ControlMeta(Over control, bool stories) --> Control { +auto OverlayWidget::RendererGL::controlMeta(Over control) const -> Control { + const auto stories = [&] { + return (_owner->_stories != nullptr); + }; switch (control) { case Over::Left: return { 0, - stories ? &st::storiesLeft : &st::mediaviewLeft + stories() ? &st::storiesLeft : &st::mediaviewLeft }; case Over::Right: return { 1, - stories ? &st::storiesRight : &st::mediaviewRight + stories() ? &st::storiesRight : &st::mediaviewRight + }; + case Over::Save: return { + 2, + (_owner->saveControlLocked() + ? &st::mediaviewSaveLocked + : &st::mediaviewSave) }; - case Over::Save: return { 2, &st::mediaviewSave }; case Over::Share: return { 3, &st::mediaviewShare }; case Over::Rotate: return { 4, &st::mediaviewRotate }; case Over::More: return { 5, &st::mediaviewMore }; @@ -730,14 +746,13 @@ void OverlayWidget::RendererGL::validateControls() { if (!_controlsImage.image().isNull()) { return; } - const auto stories = (_owner->_stories != nullptr); const auto metas = { - ControlMeta(Over::Left, stories), - ControlMeta(Over::Right, stories), - ControlMeta(Over::Save, stories), - ControlMeta(Over::Share, stories), - ControlMeta(Over::Rotate, stories), - ControlMeta(Over::More, stories), + controlMeta(Over::Left), + controlMeta(Over::Right), + controlMeta(Over::Save), + controlMeta(Over::Share), + controlMeta(Over::Rotate), + controlMeta(Over::More), }; auto maxWidth = 0; auto fullHeight = 0; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h index a0342518a..f64fb7e58 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h @@ -149,7 +149,7 @@ private: Ui::GL::Image _storiesSiblingParts[kStoriesSiblingPartsCount]; static constexpr auto kControlsCount = 6; - [[nodiscard]] static Control ControlMeta(Over control, bool stories); + [[nodiscard]] Control controlMeta(Over control) const; // Last one is for the over circle image. std::array _controlsTextures; @@ -158,6 +158,7 @@ private: bool _shadowsForStories = false; bool _blendingEnabled = false; + rpl::lifetime _storiesLifetime; rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 0cd3ca35e..119604e1c 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -86,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "layout/layout_document_generic_preview.h" #include "platform/platform_overlay_widget.h" +#include "settings/settings_premium.h" #include "storage/file_download.h" #include "storage/storage_account.h" #include "calls/calls_instance.h" @@ -130,6 +131,7 @@ constexpr auto kIdsPreloadAfter = 28; constexpr auto kLeftSiblingTextureIndex = 1; constexpr auto kRightSiblingTextureIndex = 2; constexpr auto kStoriesControlsOpacity = 1.; +constexpr auto kStorySavePromoDuration = 3 * crl::time(1000); class PipDelegate final : public Pip::Delegate { public: @@ -326,16 +328,18 @@ public: return _widget->_body; } bool valid() const override { - return _widget->_storiesSession != nullptr; + return _widget->_session || _widget->_storiesSession; } operator bool() const override { return valid(); } Main::Session &session() const override { - Expects(_widget->_storiesSession != nullptr); + Expects(_widget->_session || _widget->_storiesSession); - return *_widget->_storiesSession; + return _widget->_session + ? *_widget->_session + : *_widget->_storiesSession; } bool paused(ChatHelpers::PauseReason reason) const override { if (_widget->isHidden() @@ -1024,22 +1028,26 @@ QSize OverlayWidget::flipSizeByRotation(QSize size) const { return FlipSizeByRotation(size, _rotation); } -bool OverlayWidget::hasCopyMediaRestriction() const { - const auto story = _stories ? _stories->story() : nullptr; - return (story && !story->canDownload()) - || (_history && !_history->peer->allowsForwarding()) +bool OverlayWidget::hasCopyMediaRestriction(bool skipPremiumCheck) const { + if (const auto story = _stories ? _stories->story() : nullptr) { + return skipPremiumCheck + ? story->canDownloadIfPremium() + : story->canDownloadChecked(); + } + return (_history && !_history->peer->allowsForwarding()) || (_message && _message->forbidsSaving()); } -bool OverlayWidget::showCopyMediaRestriction() { - if (!hasCopyMediaRestriction()) { +bool OverlayWidget::showCopyMediaRestriction(bool skipPRemiumCheck) { + if (!hasCopyMediaRestriction(skipPRemiumCheck)) { return false; - } else if (!_history) { - return true; + } else if (_stories) { + uiShow()->showToast(tr::lng_error_nocopy_story(tr::now)); + } else if (_history) { + uiShow()->showToast(_history->peer->isBroadcast() + ? tr::lng_error_nocopy_channel(tr::now) + : tr::lng_error_nocopy_group(tr::now)); } - Ui::Toast::Show(_widget, _history->peer->isBroadcast() - ? tr::lng_error_nocopy_channel(tr::now) - : tr::lng_error_nocopy_group(tr::now)); return true; } @@ -1210,8 +1218,8 @@ void OverlayWidget::refreshNavVisibility() { } } -bool OverlayWidget::contentCanBeSaved() const { - if (hasCopyMediaRestriction()) { +bool OverlayWidget::computeSaveButtonVisible() const { + if (hasCopyMediaRestriction(true)) { return false; } else if (_photo) { return _photo->hasVideo() || _photoMedia->loaded(); @@ -1240,6 +1248,26 @@ void OverlayWidget::checkForSaveLoaded() { } } +void OverlayWidget::showPremiumDownloadPromo() { + const auto filter = [=](const auto &...) { + const auto usage = ChatHelpers::WindowUsage::PremiumPromo; + if (const auto window = uiShow()->resolveWindow(usage)) { + const auto ref = u"stories__save_stories_to_gallery"_q; + Settings::ShowPremium(window, ref); + } + return false; + }; + uiShow()->showToast({ + .text = tr::lng_stories_save_promo( + tr::now, + lt_link, + Ui::Text::Bold(tr::lng_send_as_premium_required_link(tr::now)), + Ui::Text::WithEntities), + .duration = kStorySavePromoDuration, + .filter = filter, + }); +} + void OverlayWidget::updateControls() { if (_document && documentBubbleShown()) { _docRect = QRect( @@ -1290,7 +1318,7 @@ void OverlayWidget::updateControls() { const auto overRect = QRect( QPoint(), QSize(st::mediaviewIconOver, st::mediaviewIconOver)); - _saveVisible = contentCanBeSaved(); + _saveVisible = computeSaveButtonVisible(); _shareVisible = story && story->canShare(); _rotateVisible = !_themePreviewShown && !story; const auto navRect = [&](int i) { @@ -1320,6 +1348,7 @@ void OverlayWidget::updateControls() { _saveNav = navRect(index); _saveNavOver = style::centerrect(_saveNav, overRect); _saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave); + Assert(st::mediaviewSave.size() == st::mediaviewSaveLocked.size()); const auto dNow = QDateTime::currentDateTime(); const auto d = [&] { @@ -1471,7 +1500,7 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { ? &st::mediaMenuIconArchiveStory : &st::mediaMenuIconSaveStory); } - if ((!story || story->canDownload()) + if ((!story || story->canDownloadChecked()) && _document && !_document->filepath(true).isEmpty()) { const auto text = Platform::IsMac() @@ -1533,11 +1562,13 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { [=] { deleteMedia(); }, &st::mediaMenuIconDelete); } - if (!hasCopyMediaRestriction()) { + if (!hasCopyMediaRestriction(true)) { addAction( tr::lng_mediaview_save_as(tr::now), [=] { saveAs(); }, - &st::mediaMenuIconDownload); + (saveControlLocked() + ? &st::mediaMenuIconDownloadLocked + : &st::mediaMenuIconDownload)); } if (const auto overviewType = computeOverviewType()) { @@ -2285,7 +2316,11 @@ void OverlayWidget::notifyFileDialogShown(bool shown) { } void OverlayWidget::saveAs() { - if (showCopyMediaRestriction()) { + if (showCopyMediaRestriction(true)) { + return; + } else if (hasCopyMediaRestriction()) { + Assert(_stories != nullptr); + showPremiumDownloadPromo(); return; } QString file; @@ -2421,9 +2456,13 @@ void OverlayWidget::handleDocumentClick() { void OverlayWidget::downloadMedia() { if (!_photo && !_document) { return; - } - if (Core::App().settings().askDownloadPath()) { + } else if (Core::App().settings().askDownloadPath()) { return saveAs(); + } else if (hasCopyMediaRestriction()) { + if (_stories && !hasCopyMediaRestriction(true)) { + showPremiumDownloadPromo(); + } + return; } QString path; @@ -2478,7 +2517,7 @@ void OverlayWidget::downloadMedia() { } } } else { - _saveVisible = contentCanBeSaved(); + _saveVisible = computeSaveButtonVisible(); update(_saveNavOver); } updateOver(_lastMouseMovePos); @@ -2498,7 +2537,7 @@ void OverlayWidget::downloadMedia() { } } else { if (!_photo || !_photoMedia->loaded()) { - _saveVisible = contentCanBeSaved(); + _saveVisible = computeSaveButtonVisible(); update(_saveNavOver); } else { if (!QDir().exists(path)) { @@ -3899,8 +3938,7 @@ void OverlayWidget::initThemePreview() { _themeShare->setClickedCallback([=] { QGuiApplication::clipboard()->setText( session->createInternalLinkFull("addtheme/" + slug)); - Ui::Toast::Show( - _body, + uiShow()->showToast( tr::lng_background_link_copied(tr::now)); }); } else { @@ -4198,6 +4236,10 @@ not_null OverlayWidget::storiesWrap() { } std::shared_ptr OverlayWidget::storiesShow() { + return uiShow(); +} + +std::shared_ptr OverlayWidget::uiShow() { if (!_cachedShow) { _cachedShow = std::make_shared(this); } @@ -4778,6 +4820,13 @@ void OverlayWidget::paintSaveMsgContent( p.setOpacity(1); } +bool OverlayWidget::saveControlLocked() const { + const auto story = _stories ? _stories->story() : nullptr; + return story + && story->canDownloadIfPremium() + && !story->canDownloadChecked(); +} + void OverlayWidget::paintControls( not_null renderer, float64 opacity) { @@ -4811,7 +4860,9 @@ void OverlayWidget::paintControls( _saveVisible, _saveNavOver, _saveNavIcon, - st::mediaviewSave }, + (saveControlLocked() + ? st::mediaviewSaveLocked + : st::mediaviewSave) }, { Over::Share, _shareVisible, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 06d0c0468..864e21084 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -245,6 +245,7 @@ private: void playbackPauseMusic(); void switchToPip(); [[nodiscard]] int topNotchSkip() const; + [[nodiscard]] std::shared_ptr uiShow(); not_null storiesWrap() override; std::shared_ptr storiesShow() override; @@ -310,8 +311,9 @@ private: void handleScreenChanged(QScreen *screen); - bool contentCanBeSaved() const; + [[nodiscard]] bool computeSaveButtonVisible() const; void checkForSaveLoaded(); + void showPremiumDownloadPromo(); Entity entityForUserPhotos(int index) const; Entity entityForSharedMedia(int index) const; @@ -495,8 +497,10 @@ private: void validatePhotoImage(Image *image, bool blurred); void validatePhotoCurrentImage(); - [[nodiscard]] bool hasCopyMediaRestriction() const; - [[nodiscard]] bool showCopyMediaRestriction(); + [[nodiscard]] bool hasCopyMediaRestriction( + bool skipPremiumCheck = false) const; + [[nodiscard]] bool showCopyMediaRestriction( + bool skipPRemiumCheck = false); [[nodiscard]] QSize flipSizeByRotation(QSize size) const; @@ -518,7 +522,8 @@ private: [[nodiscard]] bool contentShown() const; [[nodiscard]] bool opaqueContentShown() const; void clearStreaming(bool savePosition = true); - bool canInitStreaming() const; + [[nodiscard]] bool canInitStreaming() const; + [[nodiscard]] bool saveControlLocked() const; [[nodiscard]] bool topShadowOnTheRight() const; void applyHideWindowWorkaround(); diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp index 8e55994cc..c655ab678 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp @@ -95,8 +95,7 @@ void EmojiFlyAnimation::repaint() { } } -bool EmojiFlyAnimation::paintBadgeFrame( - not_null widget) { +bool EmojiFlyAnimation::paintBadgeFrame(not_null widget) { _target = widget; return !_fly.finished(); } diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h index 7bfd08ce2..f5dc8102b 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h @@ -25,7 +25,7 @@ public: [[nodiscard]] bool finished() const; void repaint(); - bool paintBadgeFrame(not_null widget); + bool paintBadgeFrame(not_null widget); private: const int _flySize = 0; @@ -33,7 +33,7 @@ private: Ui::RpWidget _layer; QRect _area; bool _areaUpdated = false; - QPointer _target; + QPointer _target; }; diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index dfe5430b2..641390f2e 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -126,6 +126,7 @@ mediaMenuIconCancel: icon {{ "menu/cancel", mediaviewMenuFg }}; mediaMenuIconShowInChat: icon {{ "menu/show_in_chat", mediaviewMenuFg }}; mediaMenuIconShowInFolder: icon {{ "menu/show_in_folder", mediaviewMenuFg }}; mediaMenuIconDownload: icon {{ "menu/download", mediaviewMenuFg }}; +mediaMenuIconDownloadLocked: icon {{ "menu/download_locked", mediaviewMenuFg }}; mediaMenuIconCopy: icon {{ "menu/copy", mediaviewMenuFg }}; mediaMenuIconForward: icon {{ "menu/forward", mediaviewMenuFg }}; mediaMenuIconDelete: icon {{ "menu/delete", mediaviewMenuFg }}; From 40b274e1b4a241ab5e75cb33aaf7a3e73cb11002 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Aug 2023 15:28:38 +0200 Subject: [PATCH 011/104] Add effect animation for heart-like. --- .../stories/media_stories_controller.cpp | 31 ++++++++++++------- .../media/stories/media_stories_controller.h | 5 ++- .../media/view/media_view_overlay_widget.cpp | 9 ++++-- .../ui/effects/reaction_fly_animation.cpp | 10 +++--- .../ui/effects/reaction_fly_animation.h | 1 + 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 32eae7905..9e74b82b8 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -114,6 +114,10 @@ struct SameDayRange { return result; } +[[nodiscard]] Data::ReactionId HeartReactionId() { + return { QString() + QChar(10084) }; +} + } // namespace class Controller::PhotoPlayback final { @@ -309,10 +313,11 @@ Controller::Controller(not_null delegate) _reactions->chosen( ) | rpl::start_with_next([=](HistoryView::Reactions::ChosenReaction id) { - startReactionAnimation(id.id, { - .type = Ui::MessageSendingAnimationFrom::Type::Emoji, - .globalStartGeometry = id.globalGeometry, - .frame = id.icon, + startReactionAnimation({ + .id = id.id, + .flyIcon = id.icon, + .flyFrom = _wrap->mapFromGlobal(id.globalGeometry), + .scaleOutDuration = st::fadeWrapDuration * 2, }, _wrap.get()); _replyArea->sendReaction(id.id); unfocusReply(); @@ -597,6 +602,13 @@ rpl::producer Controller::likedValue() const { void Controller::toggleLiked(bool liked) { _liked = liked; + if (liked) { + startReactionAnimation({ + .id = HeartReactionId(), + .scaleOutDuration = st::fadeWrapDuration * 2, + .effectOnly = true, + }, _replyArea->likeAnimationTarget()); + } } void Controller::showFullCaption() { @@ -1045,6 +1057,8 @@ void Controller::ready() { } _started = true; updatePlayingAllowed(); + uiShow()->session().data().reactions().preloadAnimationsFor( + HeartReactionId()); } void Controller::updateVideoPlayback(const Player::TrackState &state) { @@ -1563,17 +1577,10 @@ void Controller::updatePowerSaveBlocker(const Player::TrackState &state) { } void Controller::startReactionAnimation( - Data::ReactionId id, - Ui::MessageSendingAnimationFrom from, + Ui::ReactionFlyAnimationArgs args, not_null target) { Expects(shown()); - auto args = Ui::ReactionFlyAnimationArgs{ - .id = id, - .flyIcon = from.frame, - .flyFrom = _wrap->mapFromGlobal(from.globalStartGeometry), - .scaleOutDuration = st::fadeWrapDuration * 2, - }; _reactionAnimation = std::make_unique( _wrap, &shownUser()->owner().reactions(), diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 115e7e80a..997590929 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -35,7 +35,7 @@ class CachedIconFactory; namespace Ui { class RpWidget; -struct MessageSendingAnimationFrom; +struct ReactionFlyAnimationArgs; class EmojiFlyAnimation; class BoxContent; } // namespace Ui @@ -238,8 +238,7 @@ private: int index); void startReactionAnimation( - Data::ReactionId id, - Ui::MessageSendingAnimationFrom from, + Ui::ReactionFlyAnimationArgs from, not_null target); const not_null _delegate; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 119604e1c..1c010e0f0 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1031,8 +1031,8 @@ QSize OverlayWidget::flipSizeByRotation(QSize size) const { bool OverlayWidget::hasCopyMediaRestriction(bool skipPremiumCheck) const { if (const auto story = _stories ? _stories->story() : nullptr) { return skipPremiumCheck - ? story->canDownloadIfPremium() - : story->canDownloadChecked(); + ? !story->canDownloadIfPremium() + : !story->canDownloadChecked(); } return (_history && !_history->peer->allowsForwarding()) || (_message && _message->forbidsSaving()); @@ -1261,9 +1261,12 @@ void OverlayWidget::showPremiumDownloadPromo() { .text = tr::lng_stories_save_promo( tr::now, lt_link, - Ui::Text::Bold(tr::lng_send_as_premium_required_link(tr::now)), + Ui::Text::Link( + Ui::Text::Bold( + tr::lng_send_as_premium_required_link(tr::now))), Ui::Text::WithEntities), .duration = kStorySavePromoDuration, + .adaptive = true, .filter = filter, }); } diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp index 361eb3b7b..e46820573 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp @@ -111,11 +111,13 @@ ReactionFlyAnimation::ReactionFlyAnimation( }); return true; }; - if (!_custom && !resolve(_center, centerIcon, size)) { + generateMiniCopies(size + size / 2); + if (args.effectOnly) { + _custom = nullptr; + } else if (!_custom && !resolve(_center, centerIcon, size)) { return; } resolve(_effect, aroundAnimation, size * 2); - generateMiniCopies(size + size / 2); if (!args.flyIcon.isNull()) { _flyIcon = std::move(args.flyIcon); _fly.start(flyCallback(), 0., 1., kFlyDuration); @@ -210,8 +212,6 @@ void ReactionFlyAnimation::paintCenterFrame( QRect target, const QColor &colored, crl::time now) const { - Expects(_center || _custom); - const auto size = QSize( int(base::SafeRound(target.width() * _centerSizeMultiplier)), int(base::SafeRound(target.height() * _centerSizeMultiplier))); @@ -222,7 +222,7 @@ void ReactionFlyAnimation::paintCenterFrame( size.width(), size.height()); p.drawImage(rect, _center->frame(st::windowFg->c)); - } else { + } else if (_custom) { const auto scaled = (size.width() != _customSize); _custom->paint(p, { .textColor = colored, diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h index 595e11e86..00d9d2e8d 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h @@ -28,6 +28,7 @@ struct ReactionFlyAnimationArgs { QImage flyIcon; QRect flyFrom; crl::time scaleOutDuration = 0; + bool effectOnly = false; [[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const; }; From 7877cb0b3a60d1274b26fba3f3197fb1e57307a7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Aug 2023 08:07:37 +0200 Subject: [PATCH 012/104] Implement stories premium promo. --- .../settings/premium/stories_caption.png | Bin 0 -> 372 bytes .../settings/premium/stories_caption@2x.png | Bin 0 -> 512 bytes .../settings/premium/stories_caption@3x.png | Bin 0 -> 657 bytes .../icons/settings/premium/stories_links.png | Bin 0 -> 467 bytes .../settings/premium/stories_links@2x.png | Bin 0 -> 883 bytes .../settings/premium/stories_links@3x.png | Bin 0 -> 1356 bytes .../icons/settings/premium/stories_order.png | Bin 0 -> 835 bytes .../settings/premium/stories_order@2x.png | Bin 0 -> 1637 bytes .../settings/premium/stories_order@3x.png | Bin 0 -> 2339 bytes .../icons/settings/premium/timer.png | Bin 0 -> 608 bytes .../icons/settings/premium/timer@2x.png | Bin 0 -> 1250 bytes .../icons/settings/premium/timer@3x.png | Bin 0 -> 1883 bytes Telegram/Resources/langs/lang.strings | 26 ++++ .../SourceFiles/boxes/premium_preview_box.cpp | 147 ++++++++++++++++-- .../SourceFiles/boxes/premium_preview_box.h | 6 + .../media/stories/media_stories_stealth.cpp | 7 +- .../media/view/media_view_overlay_widget.cpp | 5 +- Telegram/SourceFiles/settings/settings.style | 12 ++ .../SourceFiles/settings/settings_premium.cpp | 63 ++------ .../ui/effects/premium_graphics.cpp | 134 ++++++++++------ .../SourceFiles/ui/effects/premium_graphics.h | 5 +- 21 files changed, 292 insertions(+), 113 deletions(-) create mode 100644 Telegram/Resources/icons/settings/premium/stories_caption.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_caption@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_caption@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_links.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_links@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_links@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_order.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_order@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/stories_order@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/timer.png create mode 100644 Telegram/Resources/icons/settings/premium/timer@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/timer@3x.png diff --git a/Telegram/Resources/icons/settings/premium/stories_caption.png b/Telegram/Resources/icons/settings/premium/stories_caption.png new file mode 100644 index 0000000000000000000000000000000000000000..d73bc90686c44b5b3b693f2a7ff6b9ea68f0bbb5 GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlfU-Sc#D46zV= z8?=$n#X;nV?0yBO#oxHSrs{uC-Ir$Xuztx`)tI z^hs0CmbhM%bzn)8MPIYR0%=ycwuPCqf)Dh(+SRw{#gi)wUhh)zyfW*5LZrKEV8y)c z?>6U3?>qjJZ^Qg$n&*oH+y%>Txrc`Cdw>7iMxE}a>G1)5k7uZzc3fM>usP>M{_SmP zt5PE`Oo_R(?X7dQ!ZJObK#8_>uY;l*j~BlA9jExi?C82_sgiO_OfK1+cj6GyNZpv? qmKyoU(QB!J*JY9AQ}#}a;@D5C5z4qW_Y@umdq$(csIerl9`oBONB+lyKGCQ z$F)AO=HNBQSf%XWeEi{Dx9P^)4T_8^4J;E52s1uhY5w}gHrcj?i!_XLXBWsEH}U;! z6F==;jotm7rXGH)-Ind`c>JTlqCjbi3TJNYm5B^O4}2T4dX{Bc^)a9Jzx?Iv<+!Hl zdOVdH^%vJ)&%V8l`~=aFje}oR%f#9 z%wH#;y=FAKb!D|pGND9SM>)f6WD%7cNk zK-IzaTc8N5vD6=Te3Z?XfI{EX)z4*}Q$iB}kvqdZ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/stories_caption@3x.png b/Telegram/Resources/icons/settings/premium/stories_caption@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..373884acea3047ca580662a60a7285959c2f99c2 GIT binary patch literal 657 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbAj}_S@6NF(iZa z?M%Z}ha5y)ed`=pyCtNxyK@d6x^_-4L48NV4i@W;n~Ovhr)CG}uy7YLi#09a5bNq) z@$;8Fmsr#p=^2KN^~-1VTsQwNE3sqdk{}5M1||ghaGpcrOdapx6KR`$Hrm~MS+eW? z`%|y3XKlTk=PsHopVE9rBRj`z_R)KnPBYAWXp&asGW%?s^6$R1y>Z26Npm+QZM^Za z#A~V0@X+&X{!eV(scTy?|)wz(kqQ%a8+&GgwD v7yg^gcaPiRi%A6DFKEeTO`hM!>Hd~&X)P{egw zsuqX-jm3+*JlJ;?2nt^<-J2`DHN@=7%NK8R8{|b;@2*<4qA4`Ape~Wc{>oMUn6QWQ zeO5X4>#C;y61{#Pp){R2-o>BY?OIO6Lar`)wb_R7On*G#1griD9oJmcrr mnSa@Jhpoh9ubS=e)qmONRV;V*TIIb46ri52elF{r5}E)ed$2_S literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/stories_links@2x.png b/Telegram/Resources/icons/settings/premium/stories_links@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..26dfa285efff645450e8e87a5b0874ec3bab64df GIT binary patch literal 883 zcmV-(1C0EMP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NE-AP12R9Fe^m@%p*zfLkonnLsj`%xpH3i64*0a=GmH`{i=^!)tVS z#Kc@#8=({&15pucDrpf8bN5h!a;Ega(l_{XR}(Za=Dxt4u|!6Eu7wRcH{9_ z(AlJHHfv@hZ{Z~SL_EbGiL;Y(VuuaOaA(7bbM6uVgTcUnsegrx%3xwp?-6EiU?ZUe zCRq{$XtI-&LWnHY`jOS-Ha_N@lCkK8pg{{A(E?hGs1^_?7&@kPMfW`Y`{dNL4$6p@ zqNt!bvWdhum7)SvDiw;r`FvJ$YdiS#dcA73I-k#3q1MN4v>urqBC3b}MEIT(z*egj z6*=y4E@>Y$r?mzL4K&%gWfF;2VSIuh!^tV4*zvCNG~D#)UOE(tUvIcSDSK=kRb2Uk zF_$x@#6fAXV}?f|s;(dAPeH1teB!w4O%yhaL0AZkmY3cI6;f3gF~4*b|1_zNQzqDTLv`#Asr002ov JPDHLkV1gwNgiZhe literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/stories_links@3x.png b/Telegram/Resources/icons/settings/premium/stories_links@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..fef135b0dbd2215849e7011f6c91eac4e4905b5c GIT binary patch literal 1356 zcmY*ZeK->c7~hA_xy59uNo}*CO_-0CL#(hVcI+{940CSdlxZP7DTlHf))bY{CGvUq zbiQXLjQLzi4BIZbp6)C{sHt8b-stXmZQ}ENxIZf}5P;Lh1^^&00id%rq9sC0003M81c0>) z+^SW8{_9dI!2icvgM|dYHvoW6yT1=9Bwlx^^s&7i$!t))EtZLD;_4b1rR_kYB}Pb? z@JAhEWT;0@cuZfZc_R{S8jHH}ljVW%4h(uxs{Z8LFLw2;QKzH|_Yhr;8Xkp;fgsE+o12@tOeTt~eE(i1llAxaZ*Fc53=A|h;4ZG*IKV;H5WS5F+`P-0 z^E$4st`QLt?bprC&8w=a;BYwV=$N~c6R7%eM^jUOL zByu1t3mA;G##QA#v|a_C(wv^-a5%)lGw8aYoKMzl!_C0?2YuUYi6@9eB9FJV)x^X^ zSy@@q;S=|!r^7O4#>Pw>T9kH*gP4=qd!Mmp^D?dGHYAiay2$b&%a!%kw^&`K(WD0d zDiVdME+M@16sxOEG4b*7g=u$k-5sYqZQR*0sHekS>VV$-oi{Za%`oJ%jX=?x&ZMqO zB$73yu9`byo%-*7VdX<6Ti(4?4#-- zl~8?tE*238fJb%I*4kV(uz)K{h*0;l0)kE^o5gamgT)V7E(S1J#zZueP8)UT&C`V8uClgnf$0ioeJ5wL$5>jT0k5-@8&{p z<6+x7TP5{Ov(lbO7#tHFR(|UiJVxs3lt#(1^$*`21glcw3pX&QB8QSMGfYpK}o#qIQb#?ec8%&DoVM~-n&U(_y;6ReTI89C^PyzViivli!vy@_}JxvxOi zEHxdIiOrw9-LvgT-0t-fnWO((GY4oSgE3q&8){GpyJgXzwY0RvPr1%({mjda`16WA zm!2UXA0MBY5mdIdwW-x=GMUV`6?B)DmiE4_S+Djkf7MlJh-v>L?wixE2ZjgNnhbF- zQ4`PZKkHELN-3B118|+m37_a)3@~R==9?5W(|9FXDf$gtPy$l%yj0cbP?&Jz1Y!hm zPFw+UCOO_nGQBfiLF<_|dXdPFT1ygNb6Jc-V++82YPBq?QTHohW!%)o;tdWBNj=oP zL7S;_P{ynuk9~p~Jo6#U$B_LYnC@`&i5uUzaN(L@Np_%k%qCK>uc6u**!94pTYhZu zNPr19XM|xT;?Dg(^7?h66FBx&QPFmG-&sEn@C#(Uvs*FuNPeUuybJHy`egpTRG)i? HW3vARc8g%i literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/stories_order.png b/Telegram/Resources/icons/settings/premium/stories_order.png new file mode 100644 index 0000000000000000000000000000000000000000..3051561dfe05210a7ae8e69b0d9df5a38b360414 GIT binary patch literal 835 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfoZL$i(`m| z;N1{!@1j7Fy(=Zd+NNZDQBdUOWHNST@6~Z^YFaQ~fm6{>Hl(?&aq@(sD@!I?2&%YG z1V2?Dw1Fp9fr-A8R{jCNDq#Y#+5& zCqoO18Rwrr{#aom_4n7Wq!U$p|2=!Q>}HOU$MyvwTDEfMj~_pN`0(MMKX;yg-pnnL zki9kPXN{fVj=g)oR_(R5y;B%4d-m+BSy$hG2g-cobar;Wefu_tt^`kCPme>Yfkf8U zTYvu8%>Jgke*OB#Pu8y0<%{K>bTVc7^ywQTd{l%OU%Y=W&cU*G?_SwI7cX8c+L^QY zrqzZ2W*oY6{p984g?lf*bdj}+aCdi~=&^)((Ta^54HsX$!K$?K`}glRZ$>_lmX^-X z&sT|@)yCny^wO-S&GSP=a9@#7qsA`6+eX%g?=zEyNTVe{uo*479ew|<4m zM<0I-@Sddk@NdS8_cw3e{Pf9)#pL^wCoUQyPRnF{eS8)uTgV$l&YwS@ot@p^-{0Qh z`1g13@`{QkIjp$;?wwxBEkUtMYKQXwP1HAbFx9Iy>)e?$TR4MOiU>25=dMs)SeuiQkf1QrXHhWAotrmLrftq) z(w%)a%}2aMVs&0@cK-!EJw2f6c`Qx`B7n@I359p#;^QYizH~|GviJvqj-yGAmIc?J z6j=(`Fz0QLUVgc-LQ%k~sH&<<%f{Y*a{7~3ueg+_oqqbJ%(~ds@TP|RJ(**I-t zdW40AEnB|a@MEi!;zW;_`1r-^dS)Jec;GEB3lq<$_SSk9SE+TKzc(d=60oPMpUXO@ GgeCwpK4DP+ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/stories_order@2x.png b/Telegram/Resources/icons/settings/premium/stories_order@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4b4f64ceb6a11804a5053b8126454e61a74cc447 GIT binary patch literal 1637 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 V`I`(6M6v(?002ovPDHLkV1l-wa@YU> literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 70306ad8f..a5cf29ecd 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1828,6 +1828,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_subtitle_gift#other" = "{user} has gifted you a {count}-months subscription for Telegram Premium."; "lng_premium_summary_subtitle_gift_me#one" = "You gifted {user} a {count}-month subscription for Telegram Premium."; "lng_premium_summary_subtitle_gift_me#other" = "You gifted {user} a {count}-months subscription for Telegram Premium."; +"lng_premium_summary_subtitle_stories" = "Upgraded Stories"; +"lng_premium_summary_about_stories" = "Priority order, stealth mode, permanent views history and more."; "lng_premium_summary_subtitle_double_limits" = "Doubled Limits"; "lng_premium_summary_about_double_limits" = "Up to 1000 channels, 20 folders, 10 pins, 20 public links, 4 accounts and more."; "lng_premium_summary_subtitle_more_upload" = "4Gb Upload Size"; @@ -1861,6 +1863,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_success" = "You've successfully subscribed to Telegram Premium!"; "lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region."; +// Upgraded Stories. +"lng_premium_stories_subtitle_order" = "Priority Order"; +"lng_premium_stories_about_order" = "Get more views as your stories are always displayed first."; + +"lng_premium_stories_subtitle_stealth" = "Stealth Mode"; +"lng_premium_stories_about_stealth" = "Hide the fact that you viewed other people's stories."; + +"lng_premium_stories_subtitle_views" = "Permanent Views History"; +"lng_premium_stories_about_views" = "Check who opens your stories – even after they expire."; + +"lng_premium_stories_subtitle_expiration" = "Expiration Durations*"; +"lng_premium_stories_about_expiration" = "Set custom expiration durations like 6 or 48 hours for your stories."; + +"lng_premium_stories_subtitle_download" = "Download Stories"; +"lng_premium_stories_about_download" = "Save other people's unprotected stories to your disk."; + +"lng_premium_stories_subtitle_caption" = "Longer Captions*"; +"lng_premium_stories_about_caption" = "Add ten times longer captions to your stories – up to 2048 symbols."; + +"lng_premium_stories_subtitle_links" = "Links and Formatting*"; +"lng_premium_stories_about_links" = "Add links and formatting in captions of your stories."; + +"lng_premium_stories_about_mobile" = "* Available when posting stories from Telegram apps for iOS and Android."; + // Doubled Limits. "lng_premium_double_limits_subtitle_channels" = "Groups and Channels"; "lng_premium_double_limits_about_channels#one" = "Join up to {count} channels and large groups"; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 20581a726..6505e31e6 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/padding_wrap.h" #include "ui/boxes/confirm_box.h" #include "ui/painter.h" +#include "settings/settings_common.h" #include "settings/settings_premium.h" #include "lottie/lottie_single_player.h" #include "history/view/media/history_view_sticker.h" @@ -92,6 +93,10 @@ void PreloadSticker(const std::shared_ptr &media) { [[nodiscard]] rpl::producer SectionTitle(PremiumPreview section) { switch (section) { + case PremiumPreview::Stories: + return tr::lng_premium_summary_subtitle_stories(); + case PremiumPreview::DoubleLimits: + return tr::lng_premium_summary_subtitle_double_limits(); case PremiumPreview::MoreUpload: return tr::lng_premium_summary_subtitle_more_upload(); case PremiumPreview::FasterDownload: @@ -122,6 +127,10 @@ void PreloadSticker(const std::shared_ptr &media) { [[nodiscard]] rpl::producer SectionAbout(PremiumPreview section) { switch (section) { + case PremiumPreview::Stories: + return tr::lng_premium_summary_about_stories(); + case PremiumPreview::DoubleLimits: + return tr::lng_premium_summary_about_double_limits(); case PremiumPreview::MoreUpload: return tr::lng_premium_summary_about_more_upload(); case PremiumPreview::FasterDownload: @@ -1117,6 +1126,56 @@ void Show(std::shared_ptr show, QImage back) { } } +void DecorateListPromoBox( + not_null box, + std::shared_ptr show, + const Descriptor &descriptor) { + const auto session = &show->session(); + + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + Data::AmPremiumValue( + session + ) | rpl::skip(1) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); + + if (const auto &hidden = descriptor.hiddenCallback) { + box->boxClosing() | rpl::start_with_next(hidden, box->lifetime()); + } + + if (session->premium()) { + box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + } else { + const auto button = Settings::CreateSubscribeButton({ + .parent = box, + .computeRef = [] { return u"double_limits"_q; }, + .show = show, + }); + + box->setShowFinishedCallback([=] { + button->startGlareAnimation(); + }); + + box->setStyle(st::premiumPreviewDoubledLimitsBox); + box->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto &padding = + st::premiumPreviewDoubledLimitsBox.buttonPadding; + button->resizeToWidth(width + - padding.left() + - padding.right()); + button->moveToLeft(padding.left(), padding.top()); + }, button->lifetime()); + box->addButton( + object_ptr::fromRaw(button)); + } +} + void Show( std::shared_ptr show, Descriptor &&descriptor) { @@ -1128,6 +1187,18 @@ void Show( descriptor.shownCallback(raw); } return; + } else if (descriptor.section == PremiumPreview::DoubleLimits) { + show->showBox(Box([=](not_null box) { + DoubledLimitsPreviewBox(box, &show->session()); + DecorateListPromoBox(box, show, descriptor); + })); + return; + } else if (descriptor.section == PremiumPreview::Stories) { + show->showBox(Box([=](not_null box) { + UpgradedStoriesPreviewBox(box, &show->session()); + DecorateListPromoBox(box, show, descriptor); + })); + return; } auto &list = Preloads(); for (auto i = begin(list); i != end(list);) { @@ -1240,11 +1311,13 @@ void PremiumUnavailableBox(not_null box) { void DoubledLimitsPreviewBox( not_null box, not_null session) { + box->setTitle(tr::lng_premium_summary_subtitle_double_limits()); + const auto limits = Data::PremiumLimits(session); auto entries = std::vector(); { const auto premium = limits.channelsPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_channels(), tr::lng_premium_double_limits_about_channels( lt_count, @@ -1256,7 +1329,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.dialogsPinnedPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_pins(), tr::lng_premium_double_limits_about_pins( lt_count, @@ -1268,7 +1341,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.channelsPublicPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_links(), tr::lng_premium_double_limits_about_links( lt_count, @@ -1280,7 +1353,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.gifsPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_gifs(), tr::lng_premium_double_limits_about_gifs( lt_count, @@ -1292,7 +1365,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.stickersFavedPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_stickers(), tr::lng_premium_double_limits_about_stickers( lt_count, @@ -1304,7 +1377,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.aboutLengthPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_bio(), tr::lng_premium_double_limits_about_bio( Ui::Text::RichLangValue), @@ -1314,7 +1387,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.captionLengthPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_captions(), tr::lng_premium_double_limits_about_captions( Ui::Text::RichLangValue), @@ -1324,7 +1397,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.dialogFiltersPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_folders(), tr::lng_premium_double_limits_about_folders( lt_count, @@ -1336,7 +1409,7 @@ void DoubledLimitsPreviewBox( } { const auto premium = limits.dialogFiltersChatsPremium(); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_folder_chats(), tr::lng_premium_double_limits_about_folder_chats( lt_count, @@ -1350,7 +1423,7 @@ void DoubledLimitsPreviewBox( const auto till = (nextMax >= Main::Domain::kPremiumMaxAccounts) ? QString::number(Main::Domain::kPremiumMaxAccounts) : (QString::number(nextMax) + QChar('+')); - entries.push_back(Ui::Premium::ListEntry{ + entries.push_back({ tr::lng_premium_double_limits_subtitle_accounts(), tr::lng_premium_double_limits_about_accounts( lt_count, @@ -1366,6 +1439,60 @@ void DoubledLimitsPreviewBox( std::move(entries)); } +void UpgradedStoriesPreviewBox( + not_null box, + not_null session) { + using namespace Ui::Text; + + box->setTitle(tr::lng_premium_summary_subtitle_stories()); + + auto entries = std::vector(); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_order(), + .about = tr::lng_premium_stories_about_order(WithEntities), + .icon = &st::settingsStoriesIconOrder, + }); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_stealth(), + .about = tr::lng_premium_stories_about_stealth(WithEntities), + .icon = &st::settingsStoriesIconStealth, + }); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_views(), + .about = tr::lng_premium_stories_about_views(WithEntities), + .icon = &st::settingsStoriesIconViews, + }); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_expiration(), + .about = tr::lng_premium_stories_about_expiration(WithEntities), + .icon = &st::settingsStoriesIconExpiration, + }); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_download(), + .about = tr::lng_premium_stories_about_download(WithEntities), + .icon = &st::settingsStoriesIconDownload, + }); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_caption(), + .about = tr::lng_premium_stories_about_caption(WithEntities), + .icon = &st::settingsStoriesIconCaption, + }); + entries.push_back({ + .title = tr::lng_premium_stories_subtitle_links(), + .about = tr::lng_premium_stories_about_links(WithEntities), + .icon = &st::settingsStoriesIconLinks, + }); + + Ui::Premium::ShowListBox( + box, + st::defaultPremiumLimits, + std::move(entries)); + + Settings::AddDividerText( + box->verticalLayout(), + tr::lng_premium_stories_about_mobile()); +} + object_ptr CreateUnlockButton( QWidget *parent, rpl::producer text) { diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index 51f9bcfbd..dbe2be446 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -41,7 +41,13 @@ void DoubledLimitsPreviewBox( not_null box, not_null session); +void UpgradedStoriesPreviewBox( + not_null box, + not_null session); + enum class PremiumPreview { + Stories, + DoubleLimits, MoreUpload, FasterDownload, VoiceToText, diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp index 8bacca224..04a78c7f2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer_rpl.h" #include "base/unixtime.h" +#include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" #include "data/data_peer_values.h" #include "data/data_session.h" @@ -295,7 +296,7 @@ struct Feature { label->resizeToWidth(width); label->move(added / 2, top); const auto inner = std::min(label->textMaxWidth(), width); - const auto right = (added / 2) + (width - inner) / 2 + inner; + const auto right = (added / 2) + (outer - inner) / 2 + inner; const auto lockTop = (label->height() - lock->height()) / 2; lock->move(right + lockLeft, top + lockTop); }; @@ -351,9 +352,7 @@ struct Feature { data->requested = false; const auto usage = ChatHelpers::WindowUsage::PremiumPromo; if (const auto window = show->resolveWindow(usage)) { - Settings::ShowPremium( - window, - u"stories__stealth_mode"_q); + ShowPremiumPreviewBox(window, PremiumPreview::Stories); window->window().activate(); } } else if (now.mode.cooldownTill > now.now) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 1c010e0f0..990936b29 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_peer_photo.h" #include "lang/lang_keys.h" #include "mainwindow.h" +#include "boxes/premium_preview_box.h" #include "core/application.h" #include "core/click_handler_types.h" #include "core/file_utilities.h" @@ -1252,8 +1253,8 @@ void OverlayWidget::showPremiumDownloadPromo() { const auto filter = [=](const auto &...) { const auto usage = ChatHelpers::WindowUsage::PremiumPromo; if (const auto window = uiShow()->resolveWindow(usage)) { - const auto ref = u"stories__save_stories_to_gallery"_q; - Settings::ShowPremium(window, ref); + ShowPremiumPreviewBox(window, PremiumPreview::Stories); + window->window().activate(); } return false; }; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 2fd6327a4..ebb44cf0a 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -109,6 +109,7 @@ settingsIconTopics: icon {{ "settings/topics", settingsIconFg }}; settingsIconTTL: icon {{ "settings/ttl", settingsIconFg }}; settingsIconPhoto: icon {{ "settings/photo", settingsIconFg }}; +settingsPremiumIconStories: icon {{ "settings/stories", settingsIconFg }}; settingsPremiumIconChannelsOff: icon {{ "settings/premium/channels_off", settingsIconFg }}; settingsPremiumIconDouble: icon {{ "settings/premium/double", settingsIconFg }}; settingsPremiumIconStatus: icon {{ "settings/premium/status", settingsIconFg }}; @@ -120,6 +121,14 @@ settingsPremiumIconVoice: icon {{ "settings/premium/voice", settingsIconFg }}; settingsPremiumIconFiles: icon {{ "settings/premium/files", settingsIconFg }}; settingsPremiumIconTranslations: icon {{ "settings/premium/translations", settingsIconFg }}; +settingsStoriesIconOrder: icon {{ "settings/premium/stories_order", premiumButtonBg1 }}; +settingsStoriesIconStealth: icon {{ "menu/stealth", premiumButtonBg1 }}; +settingsStoriesIconViews: icon {{ "menu/show_in_chat", premiumButtonBg1 }}; +settingsStoriesIconExpiration: icon {{ "settings/premium/timer", premiumButtonBg1 }}; +settingsStoriesIconDownload: icon {{ "menu/download", premiumButtonBg1 }}; +settingsStoriesIconCaption: icon {{ "settings/premium/stories_caption", premiumButtonBg1 }}; +settingsStoriesIconLinks: icon {{ "settings/premium/stories_links", premiumButtonBg1 }}; + settingsTTLChatsOff: icon {{ "settings/ttl/autodelete_off", windowSubTextFg }}; settingsTTLChatsOn: icon {{ "settings/ttl/autodelete_on", windowActiveTextFg }}; @@ -473,6 +482,9 @@ settingsPremiumRowAboutPadding: margins(59px, 0px, 46px, 6px); settingsPremiumPreviewTitlePadding: margins(24px, 13px, 24px, 3px); settingsPremiumPreviewAboutPadding: margins(24px, 0px, 24px, 11px); settingsPremiumPreviewLinePadding: margins(18px, 0px, 18px, 8px); +settingsPremiumPreviewIconTitlePadding: margins(62px, 13px, 24px, 1px); +settingsPremiumPreviewIconAboutPadding: margins(62px, 0px, 24px, 0px); +settingsPremiumPreviewIconPosition: point(20px, 7px); settingsPremiumTitlePadding: margins(0px, 18px, 0px, 11px); settingsPremiumAboutTextStyle: TextStyle(defaultTextStyle) { diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 2b359b984..75eb85370 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -203,13 +203,14 @@ struct Entry { const style::icon *icon; rpl::producer title; rpl::producer description; - std::optional section; + PremiumPreview section = PremiumPreview::DoubleLimits; }; using Order = std::vector; [[nodiscard]] Order FallbackOrder() { return Order{ + u"stories"_q, u"double_limits"_q, u"more_upload"_q, u"faster_download"_q, @@ -228,12 +229,22 @@ using Order = std::vector; [[nodiscard]] base::flat_map EntryMap() { return base::flat_map{ + { + u"stories"_q, + Entry{ + &st::settingsPremiumIconStories, + tr::lng_premium_summary_subtitle_stories(), + tr::lng_premium_summary_about_stories(), + PremiumPreview::Stories, + }, + }, { u"double_limits"_q, Entry{ &st::settingsPremiumIconDouble, tr::lng_premium_summary_subtitle_double_limits(), tr::lng_premium_summary_about_double_limits(), + PremiumPreview::DoubleLimits, }, }, { @@ -1318,55 +1329,7 @@ void Premium::setupContent() { _setPaused(false); }); - if (section) { - ShowPremiumPreviewToBuy(controller, *section, hidden); - return; - } - controller->show(Box([=](not_null box) { - DoubledLimitsPreviewBox(box, &controller->session()); - - box->addTopButton(st::boxTitleClose, [=] { - box->closeBox(); - }); - - Data::AmPremiumValue( - &controller->session() - ) | rpl::skip(1) | rpl::start_with_next([=] { - box->closeBox(); - }, box->lifetime()); - - box->boxClosing( - ) | rpl::start_with_next(hidden, box->lifetime()); - - if (controller->session().premium()) { - box->addButton(tr::lng_close(), [=] { - box->closeBox(); - }); - } else { - const auto button = CreateSubscribeButton({ - controller, - box, - [] { return u"double_limits"_q; } - }); - - box->setShowFinishedCallback([=] { - button->startGlareAnimation(); - }); - - box->setStyle(st::premiumPreviewDoubledLimitsBox); - box->widthValue( - ) | rpl::start_with_next([=](int width) { - const auto &padding = - st::premiumPreviewDoubledLimitsBox.buttonPadding; - button->resizeToWidth(width - - padding.left() - - padding.right()); - button->moveToLeft(padding.left(), padding.top()); - }, button->lifetime()); - box->addButton( - object_ptr::fromRaw(button)); - } - })); + ShowPremiumPreviewToBuy(controller, section, hidden); }); iconContainers.push_back(dummy); diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index a7ce3f922..693279537 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -1018,79 +1018,123 @@ QGradientStops GiftGradientStops() { }; } +QGradientStops StoriesIconsGradientStops() { + return { + { 0., st::premiumButtonBg1->c }, + { .33, st::premiumButtonBg2->c }, + { .66, st::premiumButtonBg3->c }, + { 1., st::premiumIconBg1->c }, + }; +} + void ShowListBox( not_null box, const style::PremiumLimits &st, std::vector entries) { + box->setWidth(st::boxWideWidth); const auto &stLabel = st::defaultFlatLabel; const auto &titlePadding = st::settingsPremiumPreviewTitlePadding; - const auto &descriptionPadding = st::settingsPremiumPreviewAboutPadding; + const auto &aboutPadding = st::settingsPremiumPreviewAboutPadding; + const auto iconTitlePadding = st::settingsPremiumPreviewIconTitlePadding; + const auto iconAboutPadding = st::settingsPremiumPreviewIconAboutPadding; auto lines = std::vector(); lines.reserve(int(entries.size())); + auto icons = std::shared_ptr>(); + const auto content = box->verticalLayout(); for (auto &entry : entries) { - content->add( + const auto title = content->add( object_ptr( content, - base::take(entry.subtitle) | rpl::map(Ui::Text::Bold), + base::take(entry.title) | rpl::map(Ui::Text::Bold), stLabel), - titlePadding); + entry.icon ? iconTitlePadding : titlePadding); content->add( object_ptr( content, - base::take(entry.description), + base::take(entry.about), st::boxDividerLabel), - descriptionPadding); - - const auto limitRow = content->add( - object_ptr( - content, - st, - entry.rightNumber, - TextFactory([=, text = ProcessTextFactory(std::nullopt)]( - int n) { - if (entry.customRightText && (n == entry.rightNumber)) { - return *entry.customRightText; - } else { - return text(n); - } - }), - entry.leftNumber, - kLimitRowRatio), - st::settingsPremiumPreviewLinePadding); - lines.push_back(limitRow); + entry.icon ? iconAboutPadding : aboutPadding); + if (const auto outlined = entry.icon) { + if (!icons) { + icons = std::make_shared>(); + } + const auto index = int(icons->size()); + icons->push_back(QColor()); + const auto icon = Ui::CreateChild(content.get()); + icon->resize(outlined->size()); + title->topValue() | rpl::start_with_next([=](int y) { + const auto shift = st::settingsPremiumPreviewIconPosition; + icon->move(QPoint(0, y) + shift); + }, icon->lifetime()); + icon->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(icon); + outlined->paintInCenter(p, icon->rect(), (*icons)[index]); + }, icon->lifetime()); + } + if (entry.leftNumber || entry.rightNumber) { + auto factory = [=, text = ProcessTextFactory({})](int n) { + if (entry.customRightText && (n == entry.rightNumber)) { + return *entry.customRightText; + } else { + return text(n); + } + }; + const auto limitRow = content->add( + object_ptr( + content, + st, + entry.rightNumber, + TextFactory(std::move(factory)), + entry.leftNumber, + kLimitRowRatio), + st::settingsPremiumPreviewLinePadding); + lines.push_back(limitRow); + } } content->resizeToWidth(content->height()); - // Color lines. - Assert(lines.size() > 2); - const auto from = lines.front()->y(); - const auto to = lines.back()->y() + lines.back()->height(); + // Colors for icons. + if (icons) { + box->addSkip(st::settingsPremiumPreviewLinePadding.bottom()); - const auto partialGradient = [&] { - auto stops = Ui::Premium::FullHeightGradientStops(); - // Reverse. - for (auto &stop : stops) { - stop.first = std::abs(stop.first - 1.); + const auto stops = Ui::Premium::StoriesIconsGradientStops(); + for (auto i = 0, count = int(icons->size()); i != count; ++i) { + (*icons)[i] = anim::gradient_color_at( + stops, + (count > 1) ? (i / float64(count - 1)) : 0.); } - return PartialGradient(from, to, std::move(stops)); - }(); - - for (auto i = 0; i < int(lines.size()); i++) { - const auto &line = lines[i]; - - const auto brush = QBrush( - partialGradient.compute(line->y(), line->height())); - line->setColorOverride(brush); } - box->addSkip(st::settingsPremiumPreviewLinePadding.bottom()); - box->setTitle(tr::lng_premium_summary_subtitle_double_limits()); - box->setWidth(st::boxWideWidth); + // Color lines. + if (!lines.empty()) { + box->addSkip(st::settingsPremiumPreviewLinePadding.bottom()); + + const auto from = lines.front()->y(); + const auto to = lines.back()->y() + lines.back()->height(); + + const auto partialGradient = [&] { + auto stops = Ui::Premium::FullHeightGradientStops(); + // Reverse. + for (auto &stop : stops) { + stop.first = std::abs(stop.first - 1.); + } + return PartialGradient(from, to, std::move(stops)); + }(); + + for (auto i = 0; i < int(lines.size()); i++) { + const auto &line = lines[i]; + + const auto brush = QBrush( + partialGradient.compute(line->y(), line->height())); + line->setColorOverride(brush); + } + box->addSkip(st::settingsPremiumPreviewLinePadding.bottom()); + } } void AddGiftOptions( diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.h b/Telegram/SourceFiles/ui/effects/premium_graphics.h index 089758d93..7f932a9bd 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.h +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.h @@ -88,11 +88,12 @@ void AddAccountsRow( [[nodiscard]] QGradientStops GiftGradientStops(); struct ListEntry final { - rpl::producer subtitle; - rpl::producer description; + rpl::producer title; + rpl::producer about; int leftNumber = 0; int rightNumber = 0; std::optional customRightText; + const style::icon *icon = nullptr; }; void ShowListBox( not_null box, From 6be1a0587617730bf5a9d3b3d78ee0cd4923a972 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Aug 2023 08:40:40 +0200 Subject: [PATCH 013/104] Add NEW badge to stories premium promo. --- Telegram/Resources/langs/lang.strings | 2 ++ Telegram/SourceFiles/settings/settings.style | 11 ++++++++ .../SourceFiles/settings/settings_premium.cpp | 28 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a5cf29ecd..3b86a7dda 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1860,6 +1860,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone."; "lng_premium_summary_button" = "Subscribe for {cost} per month"; +"lng_premium_summary_new_badge" = "NEW"; + "lng_premium_success" = "You've successfully subscribed to Telegram Premium!"; "lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region."; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index ebb44cf0a..8eaa3cd8f 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -129,6 +129,17 @@ settingsStoriesIconDownload: icon {{ "menu/download", premiumButtonBg1 }}; settingsStoriesIconCaption: icon {{ "settings/premium/stories_caption", premiumButtonBg1 }}; settingsStoriesIconLinks: icon {{ "settings/premium/stories_links", premiumButtonBg1 }}; +settingsPremiumNewBadge: FlatLabel(defaultFlatLabel) { + style: TextStyle(semiboldTextStyle) { + font: font(10px semibold); + linkFont: font(10px semibold); + linkFontOver: font(10px semibold); + } + textFg: windowFgActive; +} +settingsPremiumNewBadgePosition: point(4px, 1px); +settingsPremiumNewBadgePadding: margins(4px, 1px, 4px, 1px); + settingsTTLChatsOff: icon {{ "settings/ttl/autodelete_off", windowSubTextFg }}; settingsTTLChatsOn: icon {{ "settings/ttl/autodelete_on", windowActiveTextFg }}; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 75eb85370..b515e8fa1 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -204,6 +204,7 @@ struct Entry { rpl::producer title; rpl::producer description; PremiumPreview section = PremiumPreview::DoubleLimits; + bool newBadge = false; }; using Order = std::vector; @@ -236,6 +237,7 @@ using Order = std::vector; tr::lng_premium_summary_subtitle_stories(), tr::lng_premium_summary_about_stories(), PremiumPreview::Stories, + true, }, }, { @@ -1271,6 +1273,32 @@ void Premium::setupContent() { descriptionPadding); description->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto badge = entry.newBadge + ? Ui::CreateChild>( + content, + object_ptr( + content, + tr::lng_premium_summary_new_badge(), + st::settingsPremiumNewBadge), + st::settingsPremiumNewBadgePadding) + : nullptr; + if (badge) { + badge->setAttribute(Qt::WA_TransparentForMouseEvents); + badge->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(badge); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgActive); + const auto r = st::settingsPremiumNewBadgePadding.left(); + p.drawRoundedRect(badge->rect(), r, r); + }, badge->lifetime()); + + label->geometryValue( + ) | rpl::start_with_next([=](QRect geometry) { + badge->move(st::settingsPremiumNewBadgePosition + + QPoint(label->x() + label->width(), label->y())); + }, badge->lifetime()); + } const auto dummy = Ui::CreateChild(content); dummy->setAttribute(Qt::WA_TransparentForMouseEvents); From 318d75cc632ffdfcfc624d51ed031f9e10ad0972 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Aug 2023 16:49:58 +0200 Subject: [PATCH 014/104] Update API scheme on layer 161. --- Telegram/SourceFiles/data/data_stories.cpp | 31 +++++--- Telegram/SourceFiles/data/data_stories.h | 8 +- Telegram/SourceFiles/data/data_story.cpp | 66 +++++++---------- Telegram/SourceFiles/data/data_story.h | 18 +++-- .../stories/media_stories_controller.cpp | 74 +++++-------------- .../media/stories/media_stories_controller.h | 12 +-- .../stories/media_stories_recent_views.cpp | 23 +++--- Telegram/SourceFiles/mtproto/scheme/api.tl | 14 ++-- 8 files changed, 100 insertions(+), 146 deletions(-) diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 8aa84afd2..4107f7a79 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -862,7 +862,8 @@ void Stories::activateStealthMode(Fn done) { using Flag = MTPstories_ActivateStealthMode::Flag; api->request(MTPstories_ActivateStealthMode( MTP_flags(Flag::f_past | Flag::f_future) - )).done([=](const MTPBool &result) { + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); if (done) done(); }).fail([=] { if (done) done(); @@ -1233,11 +1234,11 @@ void Stories::sendIncrementViewsRequests() { void Stories::loadViewsSlice( StoryId id, - std::optional offset, - Fn)> done) { + QString offset, + Fn done) { if (_viewsStoryId == id && _viewsOffset == offset - && (offset || _viewsRequestId)) { + && (!offset.isEmpty() || _viewsRequestId)) { if (_viewsRequestId) { _viewsDone = std::move(done); } @@ -1251,21 +1252,27 @@ void Stories::loadViewsSlice( const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage; api->request(_viewsRequestId).cancel(); _viewsRequestId = api->request(MTPstories_GetStoryViewsList( + MTP_flags(0), + MTPstring(), // q MTP_int(id), - MTP_int(offset ? offset->date : 0), - MTP_long(offset ? peerToUser(offset->peer->id).bare : 0), + MTP_string(offset), MTP_int(perPage) )).done([=](const MTPstories_StoryViewsList &result) { _viewsRequestId = 0; - auto slice = std::vector(); - const auto &data = result.data(); + auto slice = StoryViews{ + .nextOffset = data.vnext_offset().value_or_empty(), + .total = data.vcount().v, + }; _owner->processUsers(data.vusers()); - slice.reserve(data.vviews().v.size()); + slice.list.reserve(data.vviews().v.size()); for (const auto &view : data.vviews().v) { - slice.push_back({ + slice.list.push_back({ .peer = _owner->peer(peerFromUser(view.data().vuser_id())), + .reaction = (view.data().vreaction() + ? ReactionFromMTP(*view.data().vreaction()) + : Data::ReactionId()), .date = view.data().vdate().v, }); } @@ -1274,7 +1281,7 @@ void Stories::loadViewsSlice( .story = _viewsStoryId, }; if (const auto story = lookup(fullId)) { - (*story)->applyViewsSlice(_viewsOffset, slice, data.vcount().v); + (*story)->applyViewsSlice(_viewsOffset, slice); } if (const auto done = base::take(_viewsDone)) { done(std::move(slice)); @@ -1713,7 +1720,7 @@ void Stories::sendPollingViewsRequests() { return; } else if (!_viewsRequestId) { Assert(_viewsDone == nullptr); - loadViewsSlice(_pollingViews.front()->id(), std::nullopt, nullptr); + loadViewsSlice(_pollingViews.front()->id(), QString(), nullptr); } _pollingViewsTimer.callOnce(kPollViewsInterval); } diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 545acdfe0..ae91d1ffc 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -179,8 +179,8 @@ public: static constexpr auto kViewsPerPage = 50; void loadViewsSlice( StoryId id, - std::optional offset, - Fn)> done); + QString offset, + Fn done); [[nodiscard]] const StoriesIds &archive() const; [[nodiscard]] rpl::producer<> archiveChanged() const; @@ -366,8 +366,8 @@ private: base::flat_set _incrementViewsRequests; StoryId _viewsStoryId = 0; - std::optional _viewsOffset; - Fn)> _viewsDone; + QString _viewsOffset; + Fn _viewsDone; mtpRequestId _viewsRequestId = 0; base::flat_set _preloaded; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index e8389ad2c..8f629bd34 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -339,51 +339,35 @@ const std::vector> &Story::recentViewers() const { return _recentViewers; } -const std::vector &Story::viewsList() const { - return _viewsList; -} - -int Story::views() const { +const StoryViews &Story::viewsList() const { return _views; } +int Story::views() const { + return _views.total; +} + void Story::applyViewsSlice( - const std::optional &offset, - const std::vector &slice, - int total) { - const auto changed = (_views != total); - _views = total; - if (!offset) { - const auto i = _viewsList.empty() - ? end(slice) - : ranges::find(slice, _viewsList.front()); - const auto merge = (i != end(slice)) - && !ranges::contains(slice, _viewsList.back()); - if (merge) { - _viewsList.insert(begin(_viewsList), begin(slice), i); - } else { - _viewsList = slice; - } - } else if (!slice.empty()) { - const auto i = ranges::find(_viewsList, *offset); - const auto merge = (i != end(_viewsList)) - && !ranges::contains(_viewsList, slice.back()); - if (merge) { - const auto after = i + 1; - if (after == end(_viewsList)) { - _viewsList.insert(after, begin(slice), end(slice)); - } else { - const auto j = ranges::find(slice, _viewsList.back()); - if (j != end(slice)) { - _viewsList.insert(end(_viewsList), j + 1, end(slice)); - } - } + const QString &offset, + const StoryViews &slice) { + const auto changed = (_views.total != slice.total); + _views.total = slice.total; + if (offset.isEmpty()) { + _views = slice; + } else if (_views.nextOffset == offset) { + _views.list.insert( + end(_views.list), + begin(slice.list), + end(slice.list)); + _views.nextOffset = slice.nextOffset; + if (_views.nextOffset.isEmpty()) { + _views.total = int(_views.list.size()); } } - const auto known = int(_viewsList.size()); + const auto known = int(_views.list.size()); if (known >= _recentViewers.size()) { const auto take = std::min(known, kRecentViewersMax); - auto viewers = _viewsList + auto viewers = _views.list | ranges::views::take(take) | ranges::views::transform(&StoryView::peer) | ranges::to_vector; @@ -436,7 +420,7 @@ void Story::applyFields( &owner().session(), data.ventities().value_or_empty()), }; - auto views = _views; + auto views = _views.total; auto viewers = std::vector>(); if (!data.is_min()) { if (const auto info = data.vviews()) { @@ -457,7 +441,7 @@ void Story::applyFields( const auto editedChanged = (_edited != edited); const auto mediaChanged = (_media != media); const auto captionChanged = (_caption != caption); - const auto viewsChanged = (_views != views) + const auto viewsChanged = (_views.total != views) || (_recentViewers != viewers); _privacyPublic = (privacy == StoryPrivacy::Public); @@ -468,8 +452,10 @@ void Story::applyFields( _edited = edited; _pinned = pinned; _noForwards = noForwards; + if (_views.total != views) { + _views = StoryViews{ .total = views }; + } if (viewsChanged) { - _views = views; _recentViewers = std::move(viewers); } if (mediaChanged) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 70370f176..21ab368b6 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/weak_ptr.h" +#include "data/data_message_reaction_id.h" class Image; class PhotoData; @@ -58,11 +59,18 @@ struct StoryMedia { struct StoryView { not_null peer; + Data::ReactionId reaction; TimeId date = 0; friend inline bool operator==(StoryView, StoryView) = default; }; +struct StoryViews { + std::vector list; + QString nextOffset; + int total = 0; +}; + class Story final { public: Story( @@ -115,12 +123,9 @@ public: [[nodiscard]] auto recentViewers() const -> const std::vector> &; - [[nodiscard]] const std::vector &viewsList() const; + [[nodiscard]] const StoryViews &viewsList() const; [[nodiscard]] int views() const; - void applyViewsSlice( - const std::optional &offset, - const std::vector &slice, - int total); + void applyViewsSlice(const QString &offset, const StoryViews &slice); void applyChanges( StoryMedia media, @@ -140,8 +145,7 @@ private: StoryMedia _media; TextWithEntities _caption; std::vector> _recentViewers; - std::vector _viewsList; - int _views = 0; + StoryViews _views; const TimeId _date = 0; const TimeId _expires = 0; TimeId _lastUpdateTime = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 9e74b82b8..bc2233f3d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1254,12 +1254,17 @@ SiblingView Controller::sibling(SiblingType type) const { return {}; } -ViewsSlice Controller::views(PeerId offset) { +const Data::StoryViews &Controller::views(int limit, bool initial) { invalidate_weak_ptrs(&_viewsLoadGuard); - if (!offset) { + if (initial) { refreshViewsFromData(); - } else if (!sliceViewsTo(offset)) { - return { .left = _viewsSlice.left }; + } + if (_viewsSlice.total > _viewsSlice.list.size() + && _viewsSlice.list.size() < limit) { + const auto done = viewsGotMoreCallback(); + const auto user = shownUser(); + auto &stories = user->owner().stories(); + stories.loadViewsSlice(_shown.story, _viewsSlice.nextOffset, done); } return _viewsSlice; } @@ -1268,27 +1273,25 @@ rpl::producer<> Controller::moreViewsLoaded() const { return _moreViewsLoaded.events(); } -Fn)> Controller::viewsGotMoreCallback() { - return crl::guard(&_viewsLoadGuard, [=]( - const std::vector &result) { +Fn Controller::viewsGotMoreCallback() { + return crl::guard(&_viewsLoadGuard, [=](Data::StoryViews result) { if (_viewsSlice.list.empty()) { const auto user = shownUser(); auto &stories = user->owner().stories(); if (const auto maybeStory = stories.lookup(_shown)) { - _viewsSlice = { - .list = result, - .left = (*maybeStory)->views() - int(result.size()), - }; + _viewsSlice = (*maybeStory)->viewsList(); } else { _viewsSlice = {}; } } else { _viewsSlice.list.insert( end(_viewsSlice.list), - begin(result), - end(result)); - _viewsSlice.left - = std::max(_viewsSlice.left - int(result.size()), 0); + begin(result.list), + end(result.list)); + _viewsSlice.total = result.nextOffset.isEmpty() + ? int(_viewsSlice.list.size()) + : std::max(result.total, int(_viewsSlice.list.size())); + _viewsSlice.nextOffset = result.nextOffset; } _moreViewsLoaded.fire({}); }); @@ -1439,46 +1442,9 @@ void Controller::refreshViewsFromData() { return; } const auto story = *maybeStory; - const auto &list = story->viewsList(); + const auto &views = story->viewsList(); const auto total = story->views(); - _viewsSlice.list = list - | ranges::views::take(Data::Stories::kViewsPerPage) - | ranges::to_vector; - _viewsSlice.left = total - int(_viewsSlice.list.size()); - if (_viewsSlice.list.empty() && _viewsSlice.left > 0) { - const auto done = viewsGotMoreCallback(); - stories.loadViewsSlice(_shown.story, std::nullopt, done); - } -} - -bool Controller::sliceViewsTo(PeerId offset) { - Expects(shown()); - - const auto user = shownUser(); - auto &stories = user->owner().stories(); - const auto maybeStory = stories.lookup(_shown); - if (!maybeStory || !user->isSelf()) { - _viewsSlice = {}; - return true; - } - const auto story = *maybeStory; - const auto &list = story->viewsList(); - const auto proj = [&](const Data::StoryView &single) { - return single.peer->id; - }; - const auto i = ranges::find(list, _viewsSlice.list.back()); - const auto add = (i != end(list)) ? int(end(list) - i - 1) : 0; - const auto j = ranges::find(_viewsSlice.list, offset, proj); - Assert(j != end(_viewsSlice.list)); - if (!add && (j + 1) == end(_viewsSlice.list)) { - const auto done = viewsGotMoreCallback(); - stories.loadViewsSlice(_shown.story, _viewsSlice.list.back(), done); - return false; - } - _viewsSlice.list.erase(begin(_viewsSlice.list), j + 1); - _viewsSlice.list.insert(end(_viewsSlice.list), i + 1, end(list)); - _viewsSlice.left -= add; - return true; + _viewsSlice = story->viewsList(); } void Controller::unfocusReply() { diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 997590929..f21b97d5b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -104,11 +104,6 @@ struct Layout { friend inline bool operator==(Layout, Layout) = default; }; -struct ViewsSlice { - std::vector list; - int left = 0; -}; - class Controller final : public base::has_weak_ptr { public: explicit Controller(not_null delegate); @@ -158,7 +153,7 @@ public: void repaintSibling(not_null sibling); [[nodiscard]] SiblingView sibling(SiblingType type) const; - [[nodiscard]] ViewsSlice views(PeerId offset); + [[nodiscard]] const Data::StoryViews &views(int limit, bool initial); [[nodiscard]] rpl::producer<> moreViewsLoaded() const; void unfocusReply(); @@ -221,9 +216,8 @@ private: void moveFromShown(); void refreshViewsFromData(); - bool sliceViewsTo(PeerId offset); [[nodiscard]] auto viewsGotMoreCallback() - -> Fn)>; + -> Fn; [[nodiscard]] bool shown() const; [[nodiscard]] UserData *shownUser() const; @@ -285,7 +279,7 @@ private: int _cachedSourceIndex = -1; bool _showingUnreadSources = false; - ViewsSlice _viewsSlice; + Data::StoryViews _viewsSlice; rpl::event_stream<> _moreViewsLoaded; base::has_weak_ptr _viewsLoadGuard; diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 806f21ebd..5a5567466 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -264,8 +264,8 @@ void RecentViews::showMenu() { return; } - const auto views = _controller->views(PeerId()); - if (views.list.empty() && !views.left) { + const auto views = _controller->views(kAddPerPage * 2, true); + if (views.list.empty() && !views.total) { return; } @@ -276,7 +276,7 @@ void RecentViews::showMenu() { st::storiesViewsMenu); auto count = 0; const auto added = std::min(int(views.list.size()), kAddPerPage); - const auto add = std::min(added + views.left, kAddPerPage); + const auto add = std::min(views.total, kAddPerPage); const auto now = QDateTime::currentDateTime(); for (const auto &entry : views.list) { addMenuRow(entry, now); @@ -393,23 +393,18 @@ void RecentViews::addMenuRowPlaceholder() { } void RecentViews::rebuildMenuTail() { - const auto offset = (_menuPlaceholderCount < _menuEntries.size()) - ? (end(_menuEntries) - _menuPlaceholderCount - 1)->peer->id - : PeerId(); - const auto views = _controller->views(offset); - if (views.list.empty()) { + const auto elements = _menuEntries.size() - _menuPlaceholderCount; + const auto views = _controller->views(elements + kAddPerPage, false); + if (views.list.size() <= elements) { return; } const auto now = QDateTime::currentDateTime(); const auto added = std::min( _menuPlaceholderCount + kAddPerPage, - int(views.list.size())); - auto add = added; - for (const auto &entry : views.list) { + int(views.list.size() - elements)); + for (auto i = elements, till = i + added; i != till; ++i) { + const auto &entry = views.list[i]; addMenuRow(entry, now); - if (!--add) { - break; - } } _menuEntriesCount = _menuEntriesCount.current() + added; } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index c2e1a6481..a4f1092f9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -386,6 +386,7 @@ updateStory#205a4133 user_id:long story:StoryItem = Update; updateReadStories#feb5345a user_id:long max_id:int = Update; updateStoryID#1bf335b9 id:int random_id:long = Update; updateStoriesStealthMode#2c084dc1 stealth_mode:StoriesStealthMode = Update; +updateSentStoryReaction#e3a73d20 user_id:long story_id:int reaction:Reaction = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1527,11 +1528,11 @@ messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector date:int = Mess sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage; -storyViews#d36760cf flags:# views_count:int recent_viewers:flags.0?Vector = StoryViews; +storyViews#c64c0b97 flags:# views_count:int reactions_count:int recent_viewers:flags.0?Vector = StoryViews; storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#945953ba flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews = StoryItem; +storyItem#44c457ce flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; userStories#8611a200 flags:# user_id:long max_read_id:flags.0?int stories:Vector = UserStories; @@ -1542,9 +1543,9 @@ stories.stories#4fe57df1 count:int stories:Vector users:Vector stories.userStories#37a6ff5f stories:UserStories users:Vector = stories.UserStories; -storyView#d0b0a7de flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int = StoryView; +storyView#b0bdeac5 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int reaction:flags.2?Reaction = StoryView; -stories.storyViewsList#fb3f77ac count:int views:Vector users:Vector = stories.StoryViewsList; +stories.storyViewsList#46e9b9ec flags:# count:int reactions_count:int views:Vector users:Vector next_offset:flags.0?string = stories.StoryViewsList; stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; @@ -2111,8 +2112,9 @@ stories.toggleAllStoriesHidden#7c2557c4 hidden:Bool = Bool; stories.getAllReadUserStories#729c562c = Updates; stories.readStories#edc5105b user_id:InputUser max_id:int = Vector; stories.incrementStoryViews#22126127 user_id:InputUser id:Vector = Bool; -stories.getStoryViewsList#4b3b5e97 id:int offset_date:int offset_id:long limit:int = stories.StoryViewsList; +stories.getStoryViewsList#f95f61a4 flags:# just_contacts:flags.0?true q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; stories.getStoriesViews#9a75d6a6 id:Vector = stories.StoryViews; stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink; stories.report#c95be06a user_id:InputUser id:Vector reason:ReportReason message:string = Bool; -stories.activateStealthMode#11d7ddae flags:# past:flags.0?true future:flags.1?true = Bool; +stories.activateStealthMode#57bbd166 flags:# past:flags.0?true future:flags.1?true = Updates; +stories.sendReaction#49aaa9b3 flags:# add_to_recent:flags.0?true user_id:InputUser story_id:int reaction:Reaction = Updates; From 4e78c24abf572f6c143cb5184dedcabc994ba489 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Aug 2023 17:30:49 +0200 Subject: [PATCH 015/104] Show reactions count below my stories. --- Telegram/SourceFiles/data/data_stories.cpp | 1 + Telegram/SourceFiles/data/data_story.cpp | 20 ++++++++++++++++--- Telegram/SourceFiles/data/data_story.h | 2 ++ .../stories/media_stories_controller.cpp | 9 ++++----- .../stories/media_stories_recent_views.cpp | 20 ++++++++++++++----- .../stories/media_stories_recent_views.h | 2 ++ 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 4107f7a79..586104855 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1263,6 +1263,7 @@ void Stories::loadViewsSlice( const auto &data = result.data(); auto slice = StoryViews{ .nextOffset = data.vnext_offset().value_or_empty(), + .reactions = data.vreactions_count().v, .total = data.vcount().v, }; _owner->processUsers(data.vusers()); diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 8f629bd34..30596269f 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -347,10 +347,16 @@ int Story::views() const { return _views.total; } +int Story::reactions() const { + return _views.reactions; +} + void Story::applyViewsSlice( const QString &offset, const StoryViews &slice) { - const auto changed = (_views.total != slice.total); + const auto changed = (_views.reactions != slice.reactions) + || (_views.total != slice.total); + _views.reactions = slice.reactions; _views.total = slice.total; if (offset.isEmpty()) { _views = slice; @@ -362,6 +368,11 @@ void Story::applyViewsSlice( _views.nextOffset = slice.nextOffset; if (_views.nextOffset.isEmpty()) { _views.total = int(_views.list.size()); + _views.reactions = _views.total + - ranges::count( + _views.list, + Data::ReactionId(), + &StoryView::reaction); } } const auto known = int(_views.list.size()); @@ -421,10 +432,12 @@ void Story::applyFields( data.ventities().value_or_empty()), }; auto views = _views.total; + auto reactions = _views.reactions; auto viewers = std::vector>(); if (!data.is_min()) { if (const auto info = data.vviews()) { views = info->data().vviews_count().v; + reactions = info->data().vreactions_count().v; if (const auto list = info->data().vrecent_viewers()) { viewers.reserve(list->v.size()); auto &owner = _peer->owner(); @@ -442,6 +455,7 @@ void Story::applyFields( const auto mediaChanged = (_media != media); const auto captionChanged = (_caption != caption); const auto viewsChanged = (_views.total != views) + || (_views.reactions != reactions) || (_recentViewers != viewers); _privacyPublic = (privacy == StoryPrivacy::Public); @@ -452,8 +466,8 @@ void Story::applyFields( _edited = edited; _pinned = pinned; _noForwards = noForwards; - if (_views.total != views) { - _views = StoryViews{ .total = views }; + if (_views.reactions != reactions || _views.total != views) { + _views = StoryViews{ .reactions = reactions, .total = views }; } if (viewsChanged) { _recentViewers = std::move(viewers); diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 21ab368b6..50d1dbff8 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -68,6 +68,7 @@ struct StoryView { struct StoryViews { std::vector list; QString nextOffset; + int reactions = 0; int total = 0; }; @@ -125,6 +126,7 @@ public: -> const std::vector> &; [[nodiscard]] const StoryViews &viewsList() const; [[nodiscard]] int views() const; + [[nodiscard]] int reactions() const; void applyViewsSlice(const QString &offset, const StoryViews &slice); void applyChanges( diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index bc2233f3d..67fde0447 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -880,6 +880,7 @@ void Controller::show( }); _recentViews->show({ .list = story->recentViewers(), + .reactions = story->reactions(), .total = story->views(), .valid = user->isSelf(), }); @@ -951,6 +952,7 @@ void Controller::subscribeToSession() { } else { _recentViews->show({ .list = update.story->recentViewers(), + .reactions = update.story->reactions(), .total = update.story->views(), .valid = update.story->peer()->isSelf(), }); @@ -1439,12 +1441,9 @@ void Controller::refreshViewsFromData() { const auto maybeStory = stories.lookup(_shown); if (!maybeStory || !user->isSelf()) { _viewsSlice = {}; - return; + } else { + _viewsSlice = (*maybeStory)->viewsList(); } - const auto story = *maybeStory; - const auto &views = story->viewsList(); - const auto total = story->views(); - _viewsSlice = story->viewsList(); } void Controller::unfocusReply() { diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 5a5567466..6155b9f0f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -131,7 +131,9 @@ void RecentViews::show(RecentViewsData data) { if (_data == data) { return; } - const auto totalChanged = _text.isEmpty() || (_data.total != data.total); + const auto countersChanged = _text.isEmpty() + || (_data.total != data.total) + || (_data.reactions != data.reactions); const auto usersChanged = !_userpics || (_data.list != data.list); _data = data; if (!_data.valid) { @@ -148,7 +150,7 @@ void RecentViews::show(RecentViewsData data) { if (!_userpics) { setupUserpics(); } - if (totalChanged) { + if (countersChanged) { updateText(); } if (usersChanged) { @@ -253,9 +255,13 @@ void RecentViews::updatePartsGeometry() { } void RecentViews::updateText() { - _text.setText(st::defaultTextStyle, _data.total - ? tr::lng_stories_views(tr::now, lt_count, _data.total) - : tr::lng_stories_no_views(tr::now)); + const auto text = _data.total + ? (tr::lng_stories_views(tr::now, lt_count, _data.total) + + (_data.reactions + ? (u" "_q + QChar(10084) + QString::number(_data.reactions)) + : QString())) + : tr::lng_stories_no_views(tr::now); + _text.setText(st::defaultTextStyle, text); updatePartsGeometry(); } @@ -340,6 +346,7 @@ void RecentViews::addMenuRow(Data::StoryView entry, const QDateTime &now) { return Ui::WhoReactedEntryData{ .text = peer->name(), .date = date, + .customEntityData = Data::ReactionEntityData(entry.reaction), .userpic = std::move(userpic), .callback = [=] { show->show(PrepareShortInfoBox(peer)); }, }; @@ -349,12 +356,14 @@ void RecentViews::addMenuRow(Data::StoryView entry, const QDateTime &now) { auto data = prepare(i->view); i->peer = peer; i->date = date; + i->customEntityData = data.customEntityData; i->callback = data.callback; i->action->setData(std::move(data)); } else { auto view = Ui::PeerUserpicView(); auto data = prepare(view); auto callback = data.callback; + auto customEntityData = data.customEntityData; auto action = base::make_unique_q( _menu->menu(), nullptr, @@ -366,6 +375,7 @@ void RecentViews::addMenuRow(Data::StoryView entry, const QDateTime &now) { .action = raw, .peer = peer, .date = date, + .customEntityData = std::move(customEntityData), .callback = std::move(callback), .view = std::move(view), }); diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h index 14bbdba68..294ea86f9 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h @@ -32,6 +32,7 @@ class Controller; struct RecentViewsData { std::vector> list; + int reactions = 0; int total = 0; bool valid = false; @@ -55,6 +56,7 @@ private: not_null action; PeerData *peer = nullptr; QString date; + QString customEntityData; Fn callback; Ui::PeerUserpicView view; InMemoryKey key; From ebe2088561878a03ef9050366f531950a1528b69 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Aug 2023 18:33:31 +0200 Subject: [PATCH 016/104] Parse story location marks. --- Telegram/SourceFiles/data/data_story.cpp | 60 +++++++++++++++++++++++- Telegram/SourceFiles/data/data_story.h | 27 +++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 30596269f..719c8a13c 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -31,6 +31,44 @@ namespace { using UpdateFlag = StoryUpdate::Flag; +[[nodiscard]] StoryArea ParseArea(const MTPMediaAreaCoordinates &area) { + const auto &data = area.data(); + return { + .geometry = { data.vx().v, data.vy().v, data.vw().v, data.vh().v }, + .rotation = data.vrotation().v, + }; +} + +[[nodiscard]] auto ParseLocation(const MTPMediaArea &area) +-> std::optional { + auto result = std::optional(); + area.match([&](const MTPDmediaAreaVenue &data) { + data.vgeo().match([&](const MTPDgeoPoint &geo) { + result.emplace(StoryLocation{ + .area = ParseArea(data.vcoordinates()), + .point = Data::LocationPoint(geo), + .title = qs(data.vtitle()), + .address = qs(data.vaddress()), + .provider = qs(data.vprovider()), + .venueId = qs(data.vvenue_id()), + .venueType = qs(data.vvenue_type()), + }); + }, [](const MTPDgeoPointEmpty &) { + }); + }, [&](const MTPDmediaAreaGeoPoint &data) { + data.vgeo().match([&](const MTPDgeoPoint &geo) { + result.emplace(StoryLocation{ + .area = ParseArea(data.vcoordinates()), + .point = Data::LocationPoint(geo), + }); + }, [](const MTPDgeoPointEmpty &) { + }); + }, [&](const MTPDinputMediaAreaVenue &data) { + LOG(("API Error: Unexpected inputMediaAreaVenue in API data.")); + }); + return result; +} + } // namespace class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { @@ -399,6 +437,10 @@ void Story::applyViewsSlice( } } +const std::vector &Story::locations() const { + return _locations; +} + void Story::applyChanges( StoryMedia media, const MTPDstoryItem &data, @@ -449,6 +491,15 @@ void Story::applyFields( } } } + auto locations = std::vector(); + if (const auto areas = data.vmedia_areas()) { + locations.reserve(areas->v.size()); + for (const auto &area : areas->v) { + if (const auto parsed = ParseLocation(area)) { + locations.push_back(*parsed); + } + } + } const auto pinnedChanged = (_pinned != pinned); const auto editedChanged = (_edited != edited); @@ -457,6 +508,7 @@ void Story::applyFields( const auto viewsChanged = (_views.total != views) || (_views.reactions != reactions) || (_recentViewers != viewers); + const auto locationsChanged = (_locations != locations); _privacyPublic = (privacy == StoryPrivacy::Public); _privacyCloseFriends = (privacy == StoryPrivacy::CloseFriends); @@ -478,8 +530,14 @@ void Story::applyFields( if (captionChanged) { _caption = std::move(caption); } + if (locationsChanged) { + _locations = std::move(locations); + } - const auto changed = (editedChanged || captionChanged || mediaChanged); + const auto changed = editedChanged + || captionChanged + || mediaChanged + || locationsChanged; if (!initial && (changed || viewsChanged)) { _peer->session().changes().storyUpdated(this, UpdateFlag() | (changed ? UpdateFlag::Edited : UpdateFlag()) diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 50d1dbff8..d288ee293 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/weak_ptr.h" +#include "data/data_location.h" #include "data/data_message_reaction_id.h" class Image; @@ -72,6 +73,29 @@ struct StoryViews { int total = 0; }; +struct StoryArea { + QRectF geometry; + float64 rotation = 0; + + friend inline bool operator==( + const StoryArea &, + const StoryArea &) = default; +}; + +struct StoryLocation { + StoryArea area; + Data::LocationPoint point; + QString title; + QString address; + QString provider; + QString venueId; + QString venueType; + + friend inline bool operator==( + const StoryLocation &, + const StoryLocation &) = default; +}; + class Story final { public: Story( @@ -129,6 +153,8 @@ public: [[nodiscard]] int reactions() const; void applyViewsSlice(const QString &offset, const StoryViews &slice); + [[nodiscard]] const std::vector &locations() const; + void applyChanges( StoryMedia media, const MTPDstoryItem &data, @@ -147,6 +173,7 @@ private: StoryMedia _media; TextWithEntities _caption; std::vector> _recentViewers; + std::vector _locations; StoryViews _views; const TimeId _date = 0; const TimeId _expires = 0; From 066dbfe8fc5e5e8ea89df9b029ce26ef1c4b15cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 7 Aug 2023 11:21:03 +0200 Subject: [PATCH 017/104] Handle clicks on location areas in stories. --- Telegram/SourceFiles/data/data_story.cpp | 5 +- .../stories/media_stories_controller.cpp | 68 ++++++++++++++++++- .../media/stories/media_stories_controller.h | 10 +++ .../media/stories/media_stories_view.cpp | 4 ++ .../media/stories/media_stories_view.h | 1 + .../media/view/media_view_overlay_widget.cpp | 3 + 6 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 719c8a13c..3ac256ffa 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -33,8 +33,11 @@ using UpdateFlag = StoryUpdate::Flag; [[nodiscard]] StoryArea ParseArea(const MTPMediaAreaCoordinates &area) { const auto &data = area.data(); + const auto center = QPointF(data.vx().v, data.vy().v); + const auto size = QSizeF(data.vw().v, data.vh().v); + const auto corner = center - QPointF(size.width(), size.height()) / 2.; return { - .geometry = { data.vx().v, data.vy().v, data.vw().v, data.vh().v }, + .geometry = { corner / 100., size / 100. }, .rotation = data.vrotation().v, }; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 67fde0447..bdff3f368 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -118,6 +118,19 @@ struct SameDayRange { return { QString() + QChar(10084) }; } +[[nodiscard]] QPoint Rotated(QPoint point, QPoint origin, float64 angle) { + if (std::abs(angle) < 1.) { + return point; + } + const auto alpha = angle / 180. * M_PI; + const auto acos = cos(alpha); + const auto asin = sin(alpha); + point -= origin; + return origin + QPoint( + int(base::SafeRound(acos * point.x() - asin * point.y())), + int(base::SafeRound(asin * point.x() + acos * point.y()))); +} + } // namespace class Controller::PhotoPlayback final { @@ -531,11 +544,30 @@ void Controller::initLayout() { .nameBoundingRect = nameBoundingRect(right, false), .nameFontSize = nameFontSize, }; - + if (!_locationAreas.empty()) { + rebuildLocationAreas(layout); + } return layout; }); } +void Controller::rebuildLocationAreas(const Layout &layout) const { + Expects(_locations.size() == _locationAreas.size()); + + const auto origin = layout.content.topLeft(); + const auto scale = layout.content.size(); + for (auto i = 0, count = int(_locations.size()); i != count; ++i) { + auto &area = _locationAreas[i]; + const auto &general = _locations[i].area.geometry; + area.geometry = QRect( + int(base::SafeRound(general.x() * scale.width())), + int(base::SafeRound(general.y() * scale.height())), + int(base::SafeRound(general.width() * scale.width())), + int(base::SafeRound(general.height() * scale.height())) + ).translated(origin); + } +} + Data::Story *Controller::story() const { if (!_session) { return nullptr; @@ -918,6 +950,14 @@ bool Controller::changeShown(Data::Story *story) { Data::Stories::Polling::Viewer); } _liked = false; + const auto &locations = story + ? story->locations() + : std::vector(); + if (_locations != locations) { + _locations = locations; + _locationAreas.clear(); + } + return true; } @@ -1082,6 +1122,32 @@ void Controller::updatePlayback(const Player::TrackState &state) { } } +ClickHandlerPtr Controller::lookupLocationHandler(QPoint point) const { + const auto &layout = _layout.current(); + if (_locations.empty() || !layout) { + return nullptr; + } else if (_locationAreas.empty()) { + _locationAreas = _locations | ranges::views::transform([]( + const Data::StoryLocation &location) { + return LocationArea{ + .rotation = location.area.rotation, + .handler = std::make_shared( + location.point), + }; + }) | ranges::to_vector; + rebuildLocationAreas(*layout); + } + + for (const auto &area : _locationAreas) { + const auto center = area.geometry.center(); + const auto angle = -area.rotation; + if (area.geometry.contains(Rotated(point, center, angle))) { + return area.handler; + } + } + return nullptr; +} + void Controller::maybeMarkAsRead(const Player::TrackState &state) { const auto length = state.length; const auto position = Player::IsStoppedAtEnd(state.state) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index f21b97d5b..b4a37cc57 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -135,6 +135,7 @@ public: void ready(); void updateVideoPlayback(const Player::TrackState &state); + [[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; [[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpFor(int delta); @@ -188,6 +189,11 @@ private: return peerId != 0; } }; + struct LocationArea { + QRect geometry; + float64 rotation = 0.; + ClickHandlerPtr handler; + }; class PhotoPlayback; class Unsupported; @@ -203,6 +209,7 @@ private: void updateContentFaded(); void updatePlayingAllowed(); void setPlayingAllowed(bool allowed); + void rebuildLocationAreas(const Layout &layout) const; void hideSiblings(); void showSiblings(not_null session); @@ -275,6 +282,9 @@ private: bool _started = false; bool _viewed = false; + std::vector _locations; + mutable std::vector _locationAreas; + std::vector _cachedSourcesList; int _cachedSourceIndex = -1; bool _showingUnreadSources = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index c3cdbd01b..1aa545726 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -59,6 +59,10 @@ void View::updatePlayback(const Player::TrackState &state) { _controller->updateVideoPlayback(state); } +ClickHandlerPtr View::lookupLocationHandler(QPoint point) const { + return _controller->lookupLocationHandler(point); +} + bool View::subjumpAvailable(int delta) const { return _controller->subjumpAvailable(delta); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index b3dee4aa5..e70b0a6c9 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -73,6 +73,7 @@ public: void showFullCaption(); void updatePlayback(const Player::TrackState &state); + [[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; [[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpFor(int delta) const; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 990936b29..01e6ba1fc 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -5666,6 +5666,9 @@ void OverlayWidget::updateOver(QPoint pos) { const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop); lnk = _groupThumbs->getState(point); lnkhost = this; + } else if (_stories) { + lnk = _stories->lookupLocationHandler(pos); + lnkhost = this; } From 13f67d68c47cdeeffa39c3a2bbf5beac617a18e7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Aug 2023 10:55:12 +0200 Subject: [PATCH 018/104] Implement custom reactions in stories. --- Telegram/SourceFiles/data/data_changes.h | 3 +- .../data/data_message_reactions.cpp | 2 +- Telegram/SourceFiles/data/data_stories.cpp | 15 + Telegram/SourceFiles/data/data_stories.h | 2 + Telegram/SourceFiles/data/data_story.cpp | 23 +- Telegram/SourceFiles/data/data_story.h | 4 + .../history_view_compose_controls.cpp | 11 +- .../controls/history_view_compose_controls.h | 3 +- .../history_view_reactions_selector.cpp | 9 +- .../history_view_reactions_selector.h | 1 + .../stories/media_stories_controller.cpp | 114 +--- .../media/stories/media_stories_controller.h | 23 +- .../media/stories/media_stories_reactions.cpp | 512 ++++++++++++++++-- .../media/stories/media_stories_reactions.h | 95 +++- .../media/stories/media_stories_reply.cpp | 18 +- .../media/stories/media_stories_reply.h | 8 +- .../SourceFiles/media/view/media_view.style | 3 +- .../ui/effects/emoji_fly_animation.cpp | 8 + .../ui/effects/emoji_fly_animation.h | 3 + .../ui/effects/reaction_fly_animation.cpp | 64 ++- .../ui/effects/reaction_fly_animation.h | 15 + 21 files changed, 744 insertions(+), 192 deletions(-) diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index d4fee22c8..304749900 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -226,8 +226,9 @@ struct StoryUpdate { NewAdded = (1U << 2), ViewsAdded = (1U << 3), MarkRead = (1U << 4), + Reaction = (1U << 5), - LastUsedBit = (1U << 4), + LastUsedBit = (1U << 5), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index e4279cbb3..7aa820101 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -381,7 +381,7 @@ void Reactions::preloadImageFor(const ReactionId &id) { loadImage(set, document, !i->centerIcon); } else if (!_waitingForList) { _waitingForList = true; - refreshRecent(); + refreshDefault(); } } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 586104855..d795b41e3 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -870,6 +870,21 @@ void Stories::activateStealthMode(Fn done) { }).send(); } +void Stories::sendReaction(FullStoryId id, Data::ReactionId reaction) { + if (const auto maybeStory = lookup(id)) { + const auto story = *maybeStory; + story->setReactionId(reaction); + + const auto api = &session().api(); + api->request(MTPstories_SendReaction( + MTP_flags(0), + story->peer()->asUser()->inputUser, + MTP_int(id.story), + ReactionToMTP(reaction) + )).send(); + } +} + std::shared_ptr Stories::resolveItem(not_null story) { auto &items = _items[story->peer()->id]; auto i = items.find(story->id()); diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index ae91d1ffc..8d8b23da4 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -240,6 +240,8 @@ public: [[nodiscard]] rpl::producer stealthModeValue() const; void activateStealthMode(Fn done = nullptr); + void sendReaction(FullStoryId id, Data::ReactionId reaction); + private: struct Saved { StoriesIds ids; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 3ac256ffa..d5faa4350 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -376,6 +376,17 @@ const TextWithEntities &Story::caption() const { return unsupported() ? empty : _caption; } +Data::ReactionId Story::sentReactionId() const { + return _sentReactionId; +} + +void Story::setReactionId(Data::ReactionId id) { + if (_sentReactionId != id) { + _sentReactionId = id; + session().changes().storyUpdated(this, UpdateFlag::Reaction); + } +} + const std::vector> &Story::recentViewers() const { return _recentViewers; } @@ -458,6 +469,9 @@ void Story::applyFields( bool initial) { _lastUpdateTime = now; + const auto reaction = data.vsent_reaction() + ? Data::ReactionFromMTP(*data.vsent_reaction()) + : Data::ReactionId(); const auto pinned = data.is_pinned(); const auto edited = data.is_edited(); const auto privacy = data.is_public() @@ -512,6 +526,7 @@ void Story::applyFields( || (_views.reactions != reactions) || (_recentViewers != viewers); const auto locationsChanged = (_locations != locations); + const auto reactionChanged = (_sentReactionId != reaction); _privacyPublic = (privacy == StoryPrivacy::Public); _privacyCloseFriends = (privacy == StoryPrivacy::CloseFriends); @@ -536,15 +551,19 @@ void Story::applyFields( if (locationsChanged) { _locations = std::move(locations); } + if (reactionChanged) { + _sentReactionId = reaction; + } const auto changed = editedChanged || captionChanged || mediaChanged || locationsChanged; - if (!initial && (changed || viewsChanged)) { + if (!initial && (changed || viewsChanged || reactionChanged)) { _peer->session().changes().storyUpdated(this, UpdateFlag() | (changed ? UpdateFlag::Edited : UpdateFlag()) - | (viewsChanged ? UpdateFlag::ViewsAdded : UpdateFlag())); + | (viewsChanged ? UpdateFlag::ViewsAdded : UpdateFlag()) + | (reactionChanged ? UpdateFlag::Reaction : UpdateFlag())); } if (!initial && (captionChanged || mediaChanged)) { if (const auto item = _peer->owner().stories().lookupItem(this)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index d288ee293..dcfe4495c 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -146,6 +146,9 @@ public: void setCaption(TextWithEntities &&caption); [[nodiscard]] const TextWithEntities &caption() const; + [[nodiscard]] Data::ReactionId sentReactionId() const; + void setReactionId(Data::ReactionId id); + [[nodiscard]] auto recentViewers() const -> const std::vector> &; [[nodiscard]] const StoryViews &viewsList() const; @@ -170,6 +173,7 @@ private: const StoryId _id = 0; const not_null _peer; + Data::ReactionId _sentReactionId; StoryMedia _media; TextWithEntities _caption; std::vector> _recentViewers; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 6990d5b22..7ad07e755 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1241,9 +1241,12 @@ bool ComposeControls::focus() { return true; } +bool ComposeControls::focused() const { + return Ui::InFocusChain(_wrap.get()); +} + rpl::producer ComposeControls::focusedValue() const { - return rpl::single(Ui::InFocusChain(_wrap.get())) - | rpl::then(_focusChanges.events()); + return rpl::single(focused()) | rpl::then(_focusChanges.events()); } rpl::producer ComposeControls::tabbedPanelShownValue() const { @@ -3022,7 +3025,7 @@ bool ComposeControls::handleCancelRequest() { } void ComposeControls::tryProcessKeyInput(not_null e) { - if (_field->isVisible()) { + if (_field->isVisible() && !e->text().isEmpty()) { _field->setFocusFast(); QCoreApplication::sendEvent(_field->rawTextEdit(), e); } @@ -3158,7 +3161,7 @@ rpl::producer ComposeControls::fieldMenuShownValue() const { return _field->menuShownValue(); } -not_null ComposeControls::likeAnimationTarget() const { +not_null ComposeControls::likeAnimationTarget() const { Expects(_like != nullptr); return _like; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 80ad5e7b2..925555832 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -147,6 +147,7 @@ public: [[nodiscard]] int heightCurrent() const; bool focus(); + [[nodiscard]] bool focused() const; [[nodiscard]] rpl::producer focusedValue() const; [[nodiscard]] rpl::producer tabbedPanelShownValue() const; [[nodiscard]] rpl::producer<> cancelRequests() const; @@ -222,7 +223,7 @@ public: [[nodiscard]] rpl::producer recordingActiveValue() const; [[nodiscard]] rpl::producer hasSendTextValue() const; [[nodiscard]] rpl::producer fieldMenuShownValue() const; - [[nodiscard]] not_null likeAnimationTarget() const; + [[nodiscard]] not_null likeAnimationTarget() const; void applyCloudDraft(); void applyDraft( diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 4aa2370a8..0f2874c4e 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -328,6 +328,10 @@ void Selector::updateShowState( update(); } +int Selector::countAppearedWidth(float64 progress) const { + return anim::interpolate(_skipx * 2 + _size, _inner.width(), progress); +} + void Selector::paintAppearing(QPainter &p) { Expects(_strip != nullptr); @@ -340,10 +344,7 @@ void Selector::paintAppearing(QPainter &p) { _paintBuffer.fill(_st.bg->c); auto q = QPainter(&_paintBuffer); const auto extents = extentsForShadow(); - const auto appearedWidth = anim::interpolate( - _skipx * 2 + _size, - _inner.width(), - _appearProgress); + const auto appearedWidth = countAppearedWidth(_appearProgress); const auto fullWidth = _inner.x() + appearedWidth + extents.right(); const auto size = QSize(fullWidth, _outer.height()); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index b47dfa2d8..270644bdc 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -63,6 +63,7 @@ public: [[nodiscard]] QMargins extentsForShadow() const; [[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int minimalHeight() const; + [[nodiscard]] int countAppearedWidth(float64 progress) const; void setSpecialExpandTopSkip(int skip); void initGeometry(int innerTop); void beforeDestroy(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index bdff3f368..b12e56585 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -7,19 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_controller.h" -#include "base/timer.h" #include "base/power_save_blocker.h" #include "base/qt_signal_producer.h" #include "base/unixtime.h" #include "boxes/peers/prepare_short_info_box.h" #include "chat_helpers/compose/compose_show.h" #include "core/application.h" +#include "core/core_settings.h" #include "core/update_checker.h" -#include "data/stickers/data_custom_emoji.h" #include "data/data_changes.h" #include "data/data_document.h" #include "data/data_file_origin.h" -#include "data/data_message_reactions.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" @@ -40,22 +38,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/report_box.h" -#include "ui/effects/emoji_fly_animation.h" -#include "ui/effects/message_sending_animation_common.h" -#include "ui/effects/reaction_fly_animation.h" -#include "ui/layers/box_content.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/round_rect.h" -#include "ui/rp_widget.h" #include "window/window_controller.h" #include "window/window_session_controller.h" -#include "styles/style_chat.h" -#include "styles/style_chat_helpers.h" +#include "styles/style_chat_helpers.h" // defaultReportBox #include "styles/style_media_view.h" -#include "styles/style_widgets.h" #include "styles/style_boxes.h" // UserpicButton #include @@ -114,10 +105,6 @@ struct SameDayRange { return result; } -[[nodiscard]] Data::ReactionId HeartReactionId() { - return { QString() + QChar(10084) }; -} - [[nodiscard]] QPoint Rotated(QPoint point, QPoint origin, float64 angle) { if (std::abs(angle) < 1.) { return point; @@ -294,7 +281,7 @@ Controller::Controller(not_null delegate) rpl::combine( _replyArea->activeValue(), - _reactions->expandedValue(), + _reactions->activeValue(), _1 || _2 ) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](bool active) { @@ -302,38 +289,16 @@ Controller::Controller(not_null delegate) updateContentFaded(); }, _lifetime); - _replyArea->focusedValue( - ) | rpl::start_with_next([=](bool focused) { - _replyFocused = focused; - if (!_replyFocused) { - _reactions->hideIfCollapsed(); - } else if (!_hasSendText) { - _reactions->show(); - } - }, _lifetime); - - _replyArea->hasSendTextValue( - ) | rpl::start_with_next([=](bool has) { - _hasSendText = has; - if (_replyFocused) { - if (_hasSendText) { - _reactions->hide(); - } else { - _reactions->show(); - } - } - }, _lifetime); + _reactions->setReplyFieldState( + _replyArea->focusedValue(), + _replyArea->hasSendTextValue()); + if (const auto like = _replyArea->likeAnimationTarget()) { + _reactions->attachToReactionButton(like); + } _reactions->chosen( - ) | rpl::start_with_next([=](HistoryView::Reactions::ChosenReaction id) { - startReactionAnimation({ - .id = id.id, - .flyIcon = id.icon, - .flyFrom = _wrap->mapFromGlobal(id.globalGeometry), - .scaleOutDuration = st::fadeWrapDuration * 2, - }, _wrap.get()); - _replyArea->sendReaction(id.id); - unfocusReply(); + ) | rpl::start_with_next([=](Reactions::Chosen chosen) { + reactionChosen(chosen.mode, chosen.reaction); }, _lifetime); _delegate->storiesLayerShown( @@ -624,23 +589,17 @@ bool Controller::skipCaption() const { return _captionFullView != nullptr; } -bool Controller::liked() const { - return _liked.current(); +void Controller::toggleLiked() { + _reactions->toggleLiked(); } -rpl::producer Controller::likedValue() const { - return _liked.value(); -} - -void Controller::toggleLiked(bool liked) { - _liked = liked; - if (liked) { - startReactionAnimation({ - .id = HeartReactionId(), - .scaleOutDuration = st::fadeWrapDuration * 2, - .effectOnly = true, - }, _replyArea->likeAnimationTarget()); +void Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { + if (mode == ReactionsMode::Message) { + _replyArea->sendReaction(chosen.id); + } else if (const auto user = shownUser()) { + user->owner().stories().sendReaction(_shown, chosen.id); } + unfocusReply(); } void Controller::showFullCaption() { @@ -902,14 +861,15 @@ void Controller::show( _viewed = false; invalidate_weak_ptrs(&_viewsLoadGuard); _reactions->hide(); - if (_replyFocused) { + if (_replyArea->focused()) { unfocusReply(); } _replyArea->show({ .user = unsupported ? nullptr : user, .id = story->id(), - }); + }, _reactions->likedValue()); + _recentViews->show({ .list = story->recentViewers(), .reactions = story->reactions(), @@ -949,7 +909,8 @@ bool Controller::changeShown(Data::Story *story) { story, Data::Stories::Polling::Viewer); } - _liked = false; + _reactions->showLikeFrom(story); + const auto &locations = story ? story->locations() : std::vector(); @@ -1099,8 +1060,7 @@ void Controller::ready() { } _started = true; updatePlayingAllowed(); - uiShow()->session().data().reactions().preloadAnimationsFor( - HeartReactionId()); + _reactions->ready(); } void Controller::updateVideoPlayback(const Player::TrackState &state) { @@ -1291,7 +1251,7 @@ void Controller::contentPressed(bool pressed) { _captionFullView->close(); } if (pressed) { - _reactions->collapse(); + _reactions->outsidePressed(); } } @@ -1607,28 +1567,6 @@ void Controller::updatePowerSaveBlocker(const Player::TrackState &state) { [=] { return _wrap->window()->windowHandle(); }); } -void Controller::startReactionAnimation( - Ui::ReactionFlyAnimationArgs args, - not_null target) { - Expects(shown()); - - _reactionAnimation = std::make_unique( - _wrap, - &shownUser()->owner().reactions(), - std::move(args), - [=] { _reactionAnimation->repaint(); }, - Data::CustomEmojiSizeTag::Isolated); - const auto layer = _reactionAnimation->layer(); - _wrap->paintRequest() | rpl::start_with_next([=] { - if (!_reactionAnimation->paintBadgeFrame(target)) { - InvokeQueued(layer, [=] { - _reactionAnimation = nullptr; - _wrap->update(); - }); - } - }, layer->lifetime()); -} - Ui::Toast::Config PrepareTogglePinnedToast(int count, bool pinned) { return { .text = (pinned diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index b4a37cc57..d7a9c37ff 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -26,17 +26,16 @@ struct FileChosen; namespace Data { struct FileOrigin; -struct ReactionId; +class DocumentMedia; } // namespace Data namespace HistoryView::Reactions { class CachedIconFactory; +struct ChosenReaction; } // namespace HistoryView::Reactions namespace Ui { class RpWidget; -struct ReactionFlyAnimationArgs; -class EmojiFlyAnimation; class BoxContent; } // namespace Ui @@ -66,6 +65,7 @@ struct SiblingView; enum class SiblingType; struct ContentLayout; class CaptionFullView; +enum class ReactionsMode; enum class HeaderLayout { Normal, @@ -118,9 +118,7 @@ public: [[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] bool skipCaption() const; - [[nodiscard]] bool liked() const; - [[nodiscard]] rpl::producer likedValue() const; - void toggleLiked(bool liked); + void toggleLiked(); void showFullCaption(); void captionClosing(); void captionClosed(); @@ -172,6 +170,9 @@ public: [[nodiscard]] rpl::lifetime &lifetime(); private: + class PhotoPlayback; + class Unsupported; + using ChosenReaction = HistoryView::Reactions::ChosenReaction; struct StoriesList { not_null user; Data::StoriesIds ids; @@ -194,8 +195,6 @@ private: float64 rotation = 0.; ClickHandlerPtr handler; }; - class PhotoPlayback; - class Unsupported; void initLayout(); bool changeShown(Data::Story *story); @@ -238,9 +237,7 @@ private: const std::vector &lists, int index); - void startReactionAnimation( - Ui::ReactionFlyAnimationArgs from, - not_null target); + void reactionChosen(ReactionsMode mode, ChosenReaction chosen); const not_null _delegate; @@ -260,9 +257,7 @@ private: bool _contentFaded = false; bool _windowActive = false; - bool _replyFocused = false; bool _replyActive = false; - bool _hasSendText = false; bool _layerShown = false; bool _menuShown = false; bool _tooltipShown = false; @@ -273,7 +268,6 @@ private: Data::StoriesContext _context; std::optional _source; std::optional _list; - rpl::variable _liked; FullStoryId _waitingForId; int _waitingForDelta = 0; int _index = 0; @@ -297,7 +291,6 @@ private: std::unique_ptr _siblingRight; std::unique_ptr _powerSaveBlocker; - std::unique_ptr _reactionAnimation; Main::Session *_session = nullptr; rpl::lifetime _sessionLifetime; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 9d4265758..01cdcebec 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -7,13 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_reactions.h" +#include "base/event_filter.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" +#include "data/data_changes.h" +#include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_message_reactions.h" #include "data/data_session.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" +#include "ui/effects/emoji_fly_animation.h" +#include "ui/effects/reaction_fly_animation.h" +#include "ui/animated_icon.h" +#include "ui/painter.h" #include "styles/style_chat_helpers.h" #include "styles/style_media_view.h" #include "styles/style_widgets.h" @@ -21,6 +29,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media::Stories { namespace { +constexpr auto kReactionScaleOutTarget = 0.7; +constexpr auto kReactionScaleOutDuration = crl::time(1000); +constexpr auto kMessageReactionScaleOutDuration = crl::time(400); + +[[nodiscard]] Data::ReactionId HeartReactionId() { + return { QString() + QChar(10084) }; +} + [[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleReactions( not_null session) { auto result = Data::PossibleItemReactionsRef(); @@ -51,7 +67,50 @@ namespace { } // namespace -struct Reactions::Hiding { +class Reactions::Panel final { +public: + explicit Panel(not_null controller); + ~Panel(); + + [[nodiscard]] rpl::producer expandedValue() const { + return _expanded.value(); + } + [[nodiscard]] rpl::producer shownValue() const { + return _shown.value(); + } + + [[nodiscard]] rpl::producer chosen() const; + + void show(Mode mode); + void hide(Mode mode); + void hideIfCollapsed(Mode mode); + void collapse(Mode mode); + + void attachToReactionButton(not_null button); + +private: + struct Hiding; + + void create(); + void updateShowState(); + void fadeOutSelector(); + void startAnimation(); + + const not_null _controller; + + std::unique_ptr _parent; + std::unique_ptr _selector; + std::vector> _hiding; + rpl::event_stream _chosen; + Ui::Animations::Simple _showing; + rpl::variable _shownValue; + rpl::variable _expanded; + rpl::variable _mode; + rpl::variable _shown = false; + +}; + +struct Reactions::Panel::Hiding { explicit Hiding(not_null parent) : widget(parent) { } @@ -60,16 +119,24 @@ struct Reactions::Hiding { QImage frame; }; -Reactions::Reactions(not_null controller) +Reactions::Panel::Panel(not_null controller) : _controller(controller) { } -Reactions::~Reactions() = default; +Reactions::Panel::~Panel() = default; -void Reactions::show() { - if (_shown) { +auto Reactions::Panel::chosen() const -> rpl::producer { + return _chosen.events(); +} + +void Reactions::Panel::show(Mode mode) { + const auto was = _mode.current(); + if (_shown.current() && was == mode) { return; + } else if (_shown.current()) { + hide(was); } + _mode = mode; create(); if (!_selector) { return; @@ -82,8 +149,8 @@ void Reactions::show() { _parent->show(); } -void Reactions::hide() { - if (!_selector) { +void Reactions::Panel::hide(Mode mode) { + if (!_selector || _mode.current() != mode) { return; } _selector->beforeDestroy(); @@ -97,20 +164,32 @@ void Reactions::hide() { _parent = nullptr; } -void Reactions::hideIfCollapsed() { - if (!_expanded.current()) { - hide(); +void Reactions::Panel::hideIfCollapsed(Mode mode) { + if (!_expanded.current() && _mode.current() == mode) { + hide(mode); } } -void Reactions::collapse() { - if (_expanded.current()) { - hide(); - show(); +void Reactions::Panel::collapse(Mode mode) { + if (_expanded.current() && _mode.current() == mode) { + hide(mode); + show(mode); } } -void Reactions::create() { +void Reactions::Panel::attachToReactionButton(not_null button) { + base::install_event_filter(button, [=](not_null e) { + if (e->type() == QEvent::ContextMenu && !button->isHidden()) { + show(Reactions::Mode::Reaction); + return base::EventFilterResult::Cancel; + } else if (e->type() == QEvent::Hide) { + hide(Reactions::Mode::Reaction); + } + return base::EventFilterResult::Continue; + }); +} + +void Reactions::Panel::create() { auto reactions = LookupPossibleReactions( &_controller->uiShow()->session()); if (reactions.recent.empty() && !reactions.morePremiumAvailable) { @@ -119,13 +198,19 @@ void Reactions::create() { _parent = std::make_unique(_controller->wrap().get()); _parent->show(); + const auto mode = _mode.current(); + _parent->events() | rpl::start_with_next([=](not_null e) { if (e->type() == QEvent::MouseButtonPress) { const auto event = static_cast(e.get()); if (event->button() == Qt::LeftButton) { if (!_selector || !_selector->geometry().contains(event->pos())) { - collapse(); + if (mode == Mode::Message) { + collapse(mode); + } else { + hide(mode); + } } } } @@ -137,17 +222,17 @@ void Reactions::create() { _controller->uiShow(), std::move(reactions), _controller->cachedReactionIconFactory().createMethod(), - [=](bool fast) { hide(); }); + [=](bool fast) { hide(mode); }); _selector->chosen( ) | rpl::start_with_next([=]( HistoryView::Reactions::ChosenReaction reaction) { - _chosen.fire_copy(reaction); - hide(); + _chosen.fire({ .reaction = reaction, .mode = mode }); + hide(mode); }, _selector->lifetime()); _selector->premiumPromoChosen() | rpl::start_with_next([=] { - hide(); + hide(mode); ShowPremiumPreviewBox( _controller->uiShow(), PremiumPreview::InfiniteReactions); @@ -165,13 +250,23 @@ void Reactions::create() { _controller->layoutValue(), _shownValue.value() ) | rpl::start_with_next([=](const Layout &layout, float64 shown) { - const auto shift = int(base::SafeRound((full / 2.) * shown)); - _parent->setGeometry(QRect( - layout.reactions.x() + layout.reactions.width() / 2 - shift, - layout.reactions.y(), - full, - layout.reactions.height())); - const auto innerTop = layout.reactions.height() + const auto width = extents.left() + + _selector->countAppearedWidth(shown) + + extents.right(); + const auto height = layout.reactions.height(); + const auto shift = (width / 2); + const auto right = (mode == Mode::Message) + ? (layout.reactions.x() + layout.reactions.width() / 2 + shift) + : (layout.controlsBottomPosition.x() + + layout.controlsWidth + - st::storiesLikeReactionsPosition.x()); + const auto top = (mode == Mode::Message) + ? layout.reactions.y() + : (layout.controlsBottomPosition.y() + - height + - st::storiesLikeReactionsPosition.y()); + _parent->setGeometry(QRect((right - width), top, full, height)); + const auto innerTop = height - st::storiesReactionsBottomSkip - st::reactStripHeight; const auto maxAdded = innerTop - extents.top() - categoriesTop; @@ -186,11 +281,15 @@ void Reactions::create() { }, _selector->lifetime()); _selector->escapes() | rpl::start_with_next([=] { - collapse(); + if (mode == Mode::Message) { + collapse(mode); + } else { + hide(mode); + } }, _selector->lifetime()); } -void Reactions::fadeOutSelector() { +void Reactions::Panel::fadeOutSelector() { const auto wrap = _controller->wrap().get(); const auto geometry = Ui::MapFrom( wrap, @@ -226,8 +325,8 @@ void Reactions::fadeOutSelector() { }); } -void Reactions::updateShowState() { - const auto progress = _showing.value(_shown ? 1. : 0.); +void Reactions::Panel::updateShowState() { + const auto progress = _showing.value(_shown.current() ? 1. : 0.); const auto opacity = 1.; const auto appearing = _showing.animating(); const auto toggling = false; @@ -235,4 +334,355 @@ void Reactions::updateShowState() { _selector->updateShowState(progress, opacity, appearing, toggling); } +Reactions::Reactions(not_null controller) +: _controller(controller) +, _panel(std::make_unique(_controller)) { + _panel->chosen() | rpl::start_with_next([=](Chosen &&chosen) { + animateAndProcess(std::move(chosen)); + }, _lifetime); +} + +Reactions::~Reactions() = default; + +rpl::producer Reactions::activeValue() const { + using namespace rpl::mappers; + return rpl::combine( + _panel->expandedValue(), + _panel->shownValue(), + _1 || _2); +} + +auto Reactions::chosen() const -> rpl::producer { + return _chosen.events(); +} + +void Reactions::setReplyFieldState( + rpl::producer focused, + rpl::producer hasSendText) { + std::move( + focused + ) | rpl::start_with_next([=](bool focused) { + _replyFocused = focused; + if (!_replyFocused) { + _panel->hideIfCollapsed(Reactions::Mode::Message); + } else if (!_hasSendText) { + _panel->show(Reactions::Mode::Message); + } + }, _lifetime); + + std::move( + hasSendText + ) | rpl::start_with_next([=](bool has) { + _hasSendText = has; + if (_replyFocused) { + if (_hasSendText) { + _panel->hide(Reactions::Mode::Message); + } else { + _panel->show(Reactions::Mode::Message); + } + } + }, _lifetime); +} + +void Reactions::attachToReactionButton(not_null button) { + _likeButton = button; + _panel->attachToReactionButton(button); +} + +Data::ReactionId Reactions::liked() const { + return _liked.current(); +} + +rpl::producer Reactions::likedValue() const { + return _liked.value(); +} + +void Reactions::showLikeFrom(Data::Story *story) { + setLikedIdFrom(story); + + if (!story) { + _likeFromLifetime.destroy(); + return; + } + _likeFromLifetime = story->session().changes().storyUpdates( + story, + Data::StoryUpdate::Flag::Reaction + ) | rpl::start_with_next([=](const Data::StoryUpdate &update) { + setLikedIdFrom(update.story); + }); +} + +void Reactions::hide() { + _panel->hide(Reactions::Mode::Message); + _panel->hide(Reactions::Mode::Reaction); +} + +void Reactions::outsidePressed() { + _panel->hide(Reactions::Mode::Reaction); + _panel->collapse(Reactions::Mode::Message); +} + +void Reactions::toggleLiked() { + const auto liked = !_liked.current().empty(); + const auto now = liked ? Data::ReactionId() : HeartReactionId(); + if (_liked.current() != now) { + animateAndProcess({ { .id = now }, ReactionsMode::Reaction }); + } +} + +void Reactions::ready() { + if (const auto story = _controller->story()) { + story->owner().reactions().preloadAnimationsFor(HeartReactionId()); + } +} + +void Reactions::animateAndProcess(Chosen &&chosen) { + const auto like = (chosen.mode == Mode::Reaction); + const auto wrap = _controller->wrap(); + const auto target = like ? _likeButton : wrap.get(); + const auto story = _controller->story(); + if (!story || !target) { + return; + } + + auto done = like + ? setLikedIdIconInit(&story->owner(), chosen.reaction.id) + : Fn(); + const auto scaleOutDuration = like + ? kReactionScaleOutDuration + : kMessageReactionScaleOutDuration; + const auto scaleOutTarget = like ? kReactionScaleOutTarget : 0.; + + if (!chosen.reaction.id.empty()) { + startReactionAnimation({ + .id = chosen.reaction.id, + .flyIcon = chosen.reaction.icon, + .flyFrom = (chosen.reaction.globalGeometry.isEmpty() + ? QRect() + : wrap->mapFromGlobal(chosen.reaction.globalGeometry)), + .scaleOutDuration = scaleOutDuration, + .scaleOutTarget = scaleOutTarget, + }, target, std::move(done)); + } + + _chosen.fire(std::move(chosen)); +} + +void Reactions::assignLikedId(Data::ReactionId id) { + invalidate_weak_ptrs(&_likeIconGuard); + _likeIcon = nullptr; + _liked = id; +} + +Fn Reactions::setLikedIdIconInit( + not_null owner, + Data::ReactionId id, + bool force) { + if (_liked.current() != id) { + _likeIconMedia = nullptr; + } else if (!force) { + return nullptr; + } + assignLikedId(id); + if (id.empty() || !_likeButton) { + return nullptr; + } + return crl::guard(&_likeIconGuard, [=](Ui::ReactionFlyCenter center) { + if (!id.custom() && !center.icon && !_likeIconMedia) { + waitForLikeIcon(owner, id); + } else { + initLikeIcon(owner, id, std::move(center)); + } + }); +} + +void Reactions::initLikeIcon( + not_null owner, + Data::ReactionId id, + Ui::ReactionFlyCenter center) { + Expects(_likeButton != nullptr); + + _likeIcon = std::make_unique(_likeButton); + const auto icon = _likeIcon.get(); + icon->show(); + _likeButton->sizeValue() | rpl::start_with_next([=](QSize size) { + icon->setGeometry(QRect(QPoint(), size)); + }, icon->lifetime()); + + if (!id.custom() && !center.icon) { + return; + } + + struct State { + Ui::ReactionFlyCenter center; + QImage cache; + }; + const auto fly = icon->lifetime().make_state(State{ + .center = std::move(center), + }); + if (const auto customId = id.custom()) { + auto withCorrectCallback = owner->customEmojiManager().create( + customId, + [=] { icon->update(); }, + Data::CustomEmojiSizeTag::Isolated); + [[maybe_unused]] const auto load = withCorrectCallback->ready(); + fly->center.custom = std::move(withCorrectCallback); + fly->center.icon = nullptr; + } else { + fly->center.icon->jumpToStart(nullptr); + fly->center.custom = nullptr; + } + const auto paintNonCached = [=](QPainter &p) { + auto hq = PainterHighQualityEnabler(p); + + const auto size = fly->center.size; + const auto target = QRect( + (icon->width() - size) / 2, + (icon->height() - size) / 2, + size, + size); + const auto scale = fly->center.scale; + if (scale < 1.) { + const auto shift = QRectF(target).center(); + p.translate(shift); + p.scale(scale, scale); + p.translate(-shift); + } + const auto multiplier = fly->center.centerSizeMultiplier; + const auto inner = int(base::SafeRound(size * multiplier)); + if (const auto icon = fly->center.icon.get()) { + const auto rect = QRect( + target.x() + (target.width() - inner) / 2, + target.y() + (target.height() - inner) / 2, + inner, + inner); + p.drawImage(rect, icon->frame(st::windowFg->c)); + } else { + const auto customSize = fly->center.customSize; + const auto scaled = (inner != customSize); + fly->center.custom->paint(p, { + .textColor = st::windowFg->c, + .size = { customSize, customSize }, + .now = crl::now(), + .scale = (scaled ? (inner / float64(customSize)) : 1.), + .position = QPoint( + target.x() + (target.width() - customSize) / 2, + target.y() + (target.height() - customSize) / 2), + .scaled = scaled, + }); + } + }; + icon->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(icon); + if (!fly->cache.isNull()) { + p.drawImage(0, 0, fly->cache); + } else if (fly->center.icon + || fly->center.custom->readyInDefaultState()) { + const auto ratio = style::DevicePixelRatio(); + fly->cache = QImage( + icon->size() * ratio, + QImage::Format_ARGB32_Premultiplied); + fly->cache.setDevicePixelRatio(ratio); + fly->cache.fill(Qt::transparent); + auto q = QPainter(&fly->cache); + paintNonCached(q); + q.end(); + + fly->center.icon = nullptr; + fly->center.custom = nullptr; + p.drawImage(0, 0, fly->cache); + } else { + paintNonCached(p); + } + }, icon->lifetime()); +} + +void Reactions::waitForLikeIcon( + not_null owner, + Data::ReactionId id) { + _likeIconWaitLifetime = rpl::single( + rpl::empty + ) | rpl::then( + owner->reactions().defaultUpdates() + ) | rpl::map([=]() -> rpl::producer { + const auto &list = owner->reactions().list( + Data::Reactions::Type::All); + const auto i = ranges::find(list, id, &Data::Reaction::id); + if (i == end(list)) { + return rpl::single(false); + } + const auto document = i->centerIcon + ? not_null(i->centerIcon) + : i->selectAnimation; + _likeIconMedia = document->createMediaView(); + _likeIconMedia->checkStickerLarge(); + return rpl::single( + rpl::empty + ) | rpl::then( + document->session().downloaderTaskFinished() + ) | rpl::map([=] { + return _likeIconMedia->loaded(); + }); + }) | rpl::flatten_latest( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + setLikedId(owner, id, true); + + crl::on_main(&_likeIconGuard, [=] { + _likeIconMedia = nullptr; + _likeIconWaitLifetime.destroy(); + }); + }); +} + +void Reactions::setLikedIdFrom(Data::Story *story) { + if (!story) { + assignLikedId({}); + } else { + setLikedId(&story->owner(), story->sentReactionId()); + } +} + +void Reactions::setLikedId( + not_null owner, + Data::ReactionId id, + bool force) { + if (const auto done = setLikedIdIconInit(owner, id, force)) { + const auto reactions = &owner->reactions(); + done(Ui::EmojiFlyAnimation(_controller->wrap(), reactions, { + .id = id, + .scaleOutDuration = kReactionScaleOutDuration, + .scaleOutTarget = kReactionScaleOutTarget, + }, [] {}, Data::CustomEmojiSizeTag::Isolated).grabBadgeCenter()); + } +} + +void Reactions::startReactionAnimation( + Ui::ReactionFlyAnimationArgs args, + not_null target, + Fn done) { + const auto wrap = _controller->wrap(); + const auto story = _controller->story(); + _reactionAnimation = std::make_unique( + wrap, + &story->owner().reactions(), + std::move(args), + [=] { _reactionAnimation->repaint(); }, + Data::CustomEmojiSizeTag::Isolated); + const auto layer = _reactionAnimation->layer(); + wrap->paintRequest() | rpl::start_with_next([=] { + if (!_reactionAnimation->paintBadgeFrame(target)) { + InvokeQueued(layer, [=] { + _reactionAnimation = nullptr; + wrap->update(); + }); + if (done) { + done(_reactionAnimation->grabBadgeCenter()); + } + } + }, layer->lifetime()); + wrap->update(); +} + } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index ca2200d7e..5a16943a7 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -7,10 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_message_reaction_id.h" #include "ui/effects/animations.h" namespace Data { +class DocumentMedia; struct ReactionId; +class Session; +class Story; } // namespace Data namespace HistoryView::Reactions { @@ -20,47 +24,96 @@ struct ChosenReaction; namespace Ui { class RpWidget; +struct ReactionFlyAnimationArgs; +struct ReactionFlyCenter; +class EmojiFlyAnimation; } // namespace Ui namespace Media::Stories { class Controller; +enum class ReactionsMode { + Message, + Reaction, +}; + class Reactions final { public: explicit Reactions(not_null controller); ~Reactions(); - using Chosen = HistoryView::Reactions::ChosenReaction; - [[nodiscard]] rpl::producer expandedValue() const { - return _expanded.value(); - } - [[nodiscard]] rpl::producer chosen() const { - return _chosen.events(); - } + using Mode = ReactionsMode; + + template + struct ChosenWrap { + Reaction reaction; + Mode mode; + }; + using Chosen = ChosenWrap; + + [[nodiscard]] rpl::producer activeValue() const; + [[nodiscard]] rpl::producer chosen() const; + + [[nodiscard]] Data::ReactionId liked() const; + [[nodiscard]] rpl::producer likedValue() const; + void showLikeFrom(Data::Story *story); - void show(); void hide(); - void hideIfCollapsed(); - void collapse(); + void outsidePressed(); + void toggleLiked(); + void ready(); + + void setReplyFieldState( + rpl::producer focused, + rpl::producer hasSendText); + void attachToReactionButton(not_null button); private: - struct Hiding; + class Panel; - void create(); - void updateShowState(); - void fadeOutSelector(); + void animateAndProcess(Chosen &&chosen); + + void assignLikedId(Data::ReactionId id); + [[nodiscard]] Fn setLikedIdIconInit( + not_null owner, + Data::ReactionId id, + bool force = false); + void setLikedIdFrom(Data::Story *story); + void setLikedId( + not_null owner, + Data::ReactionId id, + bool force = false); + void startReactionAnimation( + Ui::ReactionFlyAnimationArgs from, + not_null target, + Fn done = nullptr); + void waitForLikeIcon( + not_null owner, + Data::ReactionId id); + void initLikeIcon( + not_null owner, + Data::ReactionId id, + Ui::ReactionFlyCenter center); const not_null _controller; + const std::unique_ptr _panel; - std::unique_ptr _parent; - std::unique_ptr _selector; - std::vector> _hiding; rpl::event_stream _chosen; - Ui::Animations::Simple _showing; - rpl::variable _shownValue; - rpl::variable _expanded; - bool _shown = false; + bool _replyFocused = false; + bool _hasSendText = false; + + Ui::RpWidget *_likeButton = nullptr; + rpl::variable _liked; + base::has_weak_ptr _likeIconGuard; + std::unique_ptr _likeIcon; + std::shared_ptr _likeIconMedia; + + std::unique_ptr _reactionAnimation; + + rpl::lifetime _likeIconWaitLifetime; + rpl::lifetime _likeFromLifetime; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 53a5c3435..48ee62d2e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -623,7 +623,7 @@ void ReplyArea::initActions() { _controls->likeToggled( ) | rpl::start_with_next([=] { - _controller->toggleLiked(!_controller->liked()); + _controller->toggleLiked(); }, _lifetime); _controls->setMimeDataHook([=]( @@ -649,7 +649,9 @@ void ReplyArea::initActions() { _controls->showFinished(); } -void ReplyArea::show(ReplyAreaData data) { +void ReplyArea::show( + ReplyAreaData data, + rpl::producer likedValue) { if (_data == data) { return; } @@ -666,7 +668,11 @@ void ReplyArea::show(ReplyAreaData data) { const auto history = user ? user->owner().history(user).get() : nullptr; _controls->setHistory({ .history = history, - .liked = _controller->likedValue(), + .liked = std::move( + likedValue + ) | rpl::map([](const Data::ReactionId &id) { + return !id.empty(); + }), }); _controls->clear(); const auto hidden = user && user->isSelf(); @@ -697,6 +703,10 @@ Main::Session &ReplyArea::session() const { return _data.user->session(); } +bool ReplyArea::focused() const { + return _controls->focused(); +} + rpl::producer ReplyArea::focusedValue() const { return _controls->focusedValue(); } @@ -725,7 +735,7 @@ void ReplyArea::tryProcessKeyInput(not_null e) { _controls->tryProcessKeyInput(e); } -not_null ReplyArea::likeAnimationTarget() const { +not_null ReplyArea::likeAnimationTarget() const { return _controls->likeAnimationTarget(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index c3c13abdf..34f40b856 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -41,6 +41,7 @@ class Session; namespace Ui { struct PreparedList; class SendFilesWay; +class RpWidget; } // namespace Ui namespace Media::Stories { @@ -60,9 +61,12 @@ public: explicit ReplyArea(not_null controller); ~ReplyArea(); - void show(ReplyAreaData data); + void show( + ReplyAreaData data, + rpl::producer likedValue); void sendReaction(const Data::ReactionId &id); + [[nodiscard]] bool focused() const; [[nodiscard]] rpl::producer focusedValue() const; [[nodiscard]] rpl::producer activeValue() const; [[nodiscard]] rpl::producer hasSendTextValue() const; @@ -70,7 +74,7 @@ public: [[nodiscard]] bool ignoreWindowMove(QPoint position) const; void tryProcessKeyInput(not_null e); - [[nodiscard]] not_null likeAnimationTarget() const; + [[nodiscard]] not_null likeAnimationTarget() const; private: class Cant; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 5eb311f76..0d6f31042 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -682,7 +682,7 @@ storiesComposeControls: ComposeControls(defaultComposeControls) { attach: storiesAttach; emoji: storiesAttachEmoji; like: storiesLike; - liked: icon{{ "chat/input_liked", settingsIconBg1 }}; + liked: icon{}; suggestions: EmojiSuggestions(defaultEmojiSuggestions) { dropdown: InnerDropdown(emojiSuggestionsDropdown) { animation: PanelAnimation(defaultPanelAnimation) { @@ -807,6 +807,7 @@ storiesReactionsPan: EmojiPan(storiesEmojiPan) { storiesReactionsWidth: 210px; storiesReactionsBottomSkip: 29px; storiesReactionsAddedTop: 200px; +storiesLikeReactionsPosition: point(85px, 30px); storiesUnsupportedLabel: FlatLabel(defaultFlatLabel) { textFg: mediaviewControlFg; diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp index c655ab678..16269c3ac 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp @@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/emoji_fly_animation.h" #include "data/stickers/data_custom_emoji.h" +#include "ui/text/text_custom_emoji.h" +#include "ui/animated_icon.h" #include "styles/style_info.h" #include "styles/style_chat.h" @@ -100,4 +102,10 @@ bool EmojiFlyAnimation::paintBadgeFrame(not_null widget) { return !_fly.finished(); } +ReactionFlyCenter EmojiFlyAnimation::grabBadgeCenter() { + auto result = _fly.takeCenter(); + result.size = _flySize; + return result; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h index f5dc8102b..f868c7267 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { +struct ReactionFlyCenter; + class EmojiFlyAnimation { public: EmojiFlyAnimation( @@ -26,6 +28,7 @@ public: void repaint(); bool paintBadgeFrame(not_null widget); + [[nodiscard]] ReactionFlyCenter grabBadgeCenter(); private: const int _flySize = 0; diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp index e46820573..8bf354c17 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp @@ -68,7 +68,8 @@ ReactionFlyAnimation::ReactionFlyAnimation( : _owner(owner) , _repaint(std::move(repaint)) , _flyFrom(args.flyFrom) -, _scaleOutDuration(args.scaleOutDuration) { +, _scaleOutDuration(args.scaleOutDuration) +, _scaleOutTarget(args.scaleOutTarget) { const auto &list = owner->list(::Data::Reactions::Type::All); auto centerIcon = (DocumentData*)nullptr; auto aroundAnimation = (DocumentData*)nullptr; @@ -86,12 +87,14 @@ ReactionFlyAnimation::ReactionFlyAnimation( aroundAnimation = owner->chooseGenericAnimation(document); } else { const auto i = ranges::find(list, args.id, &::Data::Reaction::id); - if (i == end(list) || !i->centerIcon) { + if (i == end(list)/* || !i->centerIcon*/) { return; } - centerIcon = i->centerIcon; + centerIcon = i->centerIcon + ? not_null(i->centerIcon) + : i->selectAnimation; aroundAnimation = i->aroundAnimation; - _centerSizeMultiplier = 1.; + _centerSizeMultiplier = i->centerIcon ? 1. : 0.5; } const auto resolve = [&]( std::unique_ptr &icon, @@ -139,21 +142,31 @@ QRect ReactionFlyAnimation::paintGetArea( QRect clip, crl::time now) const { const auto scale = [&] { - const auto rate = _effect ? _effect->frameRate() : 0.; - if (!_scaleOutDuration || !rate) { + if (!_scaleOutDuration + || (!_effect && !_noEffectScaleStarted)) { return 1.; } - const auto left = _effect->framesCount() - _effect->frameIndex(); - const auto duration = left * 1000. / rate; - return (duration < _scaleOutDuration) - ? (duration / double(_scaleOutDuration)) - : 1.; + auto progress = _noEffectScaleAnimation.value(0.); + if (_effect) { + const auto rate = _effect->frameRate(); + if (!rate) { + return 1.; + } + const auto left = _effect->framesCount() - _effect->frameIndex(); + const auto duration = left * 1000. / rate; + progress = (duration < _scaleOutDuration) + ? (duration / double(_scaleOutDuration)) + : 1.; + } + return (1. * progress + _scaleOutTarget * (1. - progress)); }(); + auto hq = std::optional(); if (scale < 1.) { - const auto delta = ((1. - scale) / 2.) * target.size(); - target = QRect( - target.topLeft() + QPoint(delta.width(), delta.height()), - target.size() * scale); + hq.emplace(p); + const auto shift = QRectF(target).center(); + p.translate(shift); + p.scale(scale, scale); + p.translate(-shift); } if (!_valid) { return QRect(); @@ -169,8 +182,10 @@ QRect ReactionFlyAnimation::paintGetArea( if (clip.isEmpty() || area.intersects(clip)) { paintCenterFrame(p, target, colored, now); if (const auto effect = _effect.get()) { - // Must not be colored to text. - p.drawImage(wide, effect->frame(QColor())); + if (effect->animating()) { + // Must not be colored to text. + p.drawImage(wide, effect->frame(QColor())); + } } paintMiniCopies(p, target.center(), colored, now); } @@ -359,6 +374,9 @@ void ReactionFlyAnimation::startAnimations() { } if (const auto effect = _effect.get()) { _effect->animate(callback()); + } else if (_scaleOutDuration > 0) { + _noEffectScaleStarted = true; + _noEffectScaleAnimation.start(callback(), 1, 0, _scaleOutDuration); } if (!_miniCopies.empty()) { _minis.start(callback(), 0., 1., kMiniCopiesDurationMax); @@ -382,7 +400,19 @@ bool ReactionFlyAnimation::finished() const { || (_flyIcon.isNull() && (!_center || !_center->animating()) && (!_effect || !_effect->animating()) + && !_noEffectScaleAnimation.animating() && !_minis.animating()); } +ReactionFlyCenter ReactionFlyAnimation::takeCenter() { + _valid = false; + return { + .custom = std::move(_custom), + .icon = std::move(_center), + .scale = (_scaleOutDuration > 0) ? _scaleOutTarget : 1., + .centerSizeMultiplier = _centerSizeMultiplier, + .customSize = _customSize, + }; +} + } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h index 00d9d2e8d..340deffa0 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h @@ -28,11 +28,21 @@ struct ReactionFlyAnimationArgs { QImage flyIcon; QRect flyFrom; crl::time scaleOutDuration = 0; + float64 scaleOutTarget = 0.; bool effectOnly = false; [[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const; }; +struct ReactionFlyCenter { + std::unique_ptr custom; + std::unique_ptr icon; + float64 scale = 0.; + float64 centerSizeMultiplier = 0.; + int customSize = 0; + int size = 0; +}; + class ReactionFlyAnimation final { public: ReactionFlyAnimation( @@ -56,6 +66,8 @@ public: [[nodiscard]] float64 flyingProgress() const; [[nodiscard]] bool finished() const; + [[nodiscard]] ReactionFlyCenter takeCenter(); + private: struct Parabolic { float64 a = 0.; @@ -98,6 +110,7 @@ private: std::unique_ptr _custom; std::unique_ptr _center; std::unique_ptr _effect; + Animations::Simple _noEffectScaleAnimation; std::vector _miniCopies; Animations::Simple _fly; Animations::Simple _minis; @@ -105,6 +118,8 @@ private: float64 _centerSizeMultiplier = 0.; int _customSize = 0; crl::time _scaleOutDuration = 0; + float64 _scaleOutTarget = 0.; + bool _noEffectScaleStarted = false; bool _valid = false; mutable Parabolic _cached; From e6ad367c550e4834d43cacf590cbceeb168caf18 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Aug 2023 13:02:02 +0200 Subject: [PATCH 019/104] Show reactions in story viewers list. --- .../media/stories/media_stories_recent_views.cpp | 12 +++++++----- .../media/stories/media_stories_recent_views.h | 2 +- Telegram/SourceFiles/media/view/media_view.style | 3 ++- .../ui/controls/who_reacted_context_action.cpp | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 6155b9f0f..4bfe93d64 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_who_reacted.h" // FormatReadDate. #include "chat_helpers/compose/compose_show.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_peer.h" #include "data/data_stories.h" #include "main/main_session.h" @@ -258,7 +259,7 @@ void RecentViews::updateText() { const auto text = _data.total ? (tr::lng_stories_views(tr::now, lt_count, _data.total) + (_data.reactions - ? (u" "_q + QChar(10084) + QString::number(_data.reactions)) + ? (u" "_q + QChar(10084) + QString::number(_data.reactions)) : QString())) : tr::lng_stories_no_views(tr::now); _text.setText(st::defaultTextStyle, text); @@ -281,6 +282,7 @@ void RecentViews::showMenu() { _widget.get(), st::storiesViewsMenu); auto count = 0; + const auto session = &_controller->story()->session(); const auto added = std::min(int(views.list.size()), kAddPerPage); const auto add = std::min(views.total, kAddPerPage); const auto now = QDateTime::currentDateTime(); @@ -291,7 +293,7 @@ void RecentViews::showMenu() { } } while (count++ < add) { - addMenuRowPlaceholder(); + addMenuRowPlaceholder(session); } rpl::merge( _controller->moreViewsLoaded(), @@ -366,7 +368,7 @@ void RecentViews::addMenuRow(Data::StoryView entry, const QDateTime &now) { auto customEntityData = data.customEntityData; auto action = base::make_unique_q( _menu->menu(), - nullptr, + Data::ReactedMenuFactory(&entry.peer->session()), _menu->menu()->st(), prepare(view)); const auto raw = action.get(); @@ -390,10 +392,10 @@ void RecentViews::addMenuRow(Data::StoryView entry, const QDateTime &now) { } } -void RecentViews::addMenuRowPlaceholder() { +void RecentViews::addMenuRowPlaceholder(not_null session) { auto action = base::make_unique_q( _menu->menu(), - nullptr, + Data::ReactedMenuFactory(session), _menu->menu()->st(), Ui::WhoReactedEntryData{ .preloader = true }); const auto raw = action.get(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h index 294ea86f9..d469cbb5e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h @@ -70,7 +70,7 @@ private: void showMenu(); void addMenuRow(Data::StoryView entry, const QDateTime &now); - void addMenuRowPlaceholder(); + void addMenuRowPlaceholder(not_null session); void rebuildMenuTail(); void subscribeToMenuUserpicsLoading(not_null session); void refreshClickHandler(); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 0d6f31042..db01d30fa 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -778,8 +778,9 @@ storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) { scrollPadding: margins(0px, 6px, 0px, 4px); maxHeight: 320px; menu: Menu(storiesMenuWithIcons) { + itemPadding: margins(54px, 8px, 17px, 8px); widthMin: 215px; - widthMax: 215px; + widthMax: 250px; } radius: 7px; } diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index 4a7dcb337..3be58146a 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -617,7 +617,7 @@ void WhoReactedEntryAction::paint(Painter &&p) { .textColor = (selected ? _st.itemFgOver : _st.itemFg)->c, .now = crl::now(), .position = QPoint( - width() - _st.itemPadding.right() - (size / ratio) + skip, + width() - _st.itemPadding.right() - size + skip, (height() - _customSize) / 2), }); } From 2dfaf27884cfdd3627f3285f3a9deefe2324daa6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Aug 2023 15:46:54 +0200 Subject: [PATCH 020/104] Fix colors for colorized stories reactions. --- .../SourceFiles/boxes/peers/edit_forum_topic_box.cpp | 1 + .../info/profile/info_profile_emoji_status_panel.cpp | 1 + .../media/stories/media_stories_reactions.cpp | 9 ++++++--- Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp | 4 +++- Telegram/SourceFiles/ui/effects/emoji_fly_animation.h | 2 ++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 9727f6c61..d5dbc6789 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -354,6 +354,7 @@ struct IconSelector { &owner->reactions(), std::move(args), [=] { state->animation->repaint(); }, + [] { return st::windowFg->c; }, Data::CustomEmojiSizeTag::Large); } state->iconId = id; diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp index 352aa8b45..7a9e18b92 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp @@ -229,6 +229,7 @@ void EmojiStatusPanel::startAnimation( &owner->reactions(), std::move(args), [=] { _animation->repaint(); }, + [] { return st::profileVerifiedCheckBg->c; }, _animationSizeTag); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 01cdcebec..e9162d891 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -556,12 +556,12 @@ void Reactions::initLikeIcon( target.y() + (target.height() - inner) / 2, inner, inner); - p.drawImage(rect, icon->frame(st::windowFg->c)); + p.drawImage(rect, icon->frame(st::storiesComposeWhiteText->c)); } else { const auto customSize = fly->center.customSize; const auto scaled = (inner != customSize); fly->center.custom->paint(p, { - .textColor = st::windowFg->c, + .textColor = st::storiesComposeWhiteText->c, .size = { customSize, customSize }, .now = crl::now(), .scale = (scaled ? (inner / float64(customSize)) : 1.), @@ -650,11 +650,13 @@ void Reactions::setLikedId( bool force) { if (const auto done = setLikedIdIconInit(owner, id, force)) { const auto reactions = &owner->reactions(); + const auto colored = [] { return st::storiesComposeWhiteText->c; }; + const auto sizeTag = Data::CustomEmojiSizeTag::Isolated; done(Ui::EmojiFlyAnimation(_controller->wrap(), reactions, { .id = id, .scaleOutDuration = kReactionScaleOutDuration, .scaleOutTarget = kReactionScaleOutTarget, - }, [] {}, Data::CustomEmojiSizeTag::Isolated).grabBadgeCenter()); + }, [] {}, colored, sizeTag).grabBadgeCenter()); } } @@ -669,6 +671,7 @@ void Reactions::startReactionAnimation( &story->owner().reactions(), std::move(args), [=] { _reactionAnimation->repaint(); }, + [] { return st::storiesComposeWhiteText->c; }, Data::CustomEmojiSizeTag::Isolated); const auto layer = _reactionAnimation->layer(); wrap->paintRequest() | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp index 16269c3ac..559e4ed35 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp @@ -33,8 +33,10 @@ EmojiFlyAnimation::EmojiFlyAnimation( not_null owner, ReactionFlyAnimationArgs &&args, Fn repaint, + Fn textColor, Data::CustomEmojiSizeTag tag) : _flySize(ComputeFlySize(tag)) +, _textColor(std::move(textColor)) , _fly( owner, std::move(args), @@ -63,7 +65,7 @@ EmojiFlyAnimation::EmojiFlyAnimation( QRect( rect.topLeft() + QPoint(skipx, skipy), QSize(_flySize, _flySize)), - st::infoPeerBadge.premiumFg->c, + _textColor(), clip, crl::now()); if (_areaUpdated || _area.isEmpty()) { diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h index f868c7267..cf0e60099 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h @@ -21,6 +21,7 @@ public: not_null owner, Ui::ReactionFlyAnimationArgs &&args, Fn repaint, + Fn textColor, Data::CustomEmojiSizeTag tag); [[nodiscard]] not_null layer(); @@ -32,6 +33,7 @@ public: private: const int _flySize = 0; + Fn _textColor; Ui::ReactionFlyAnimation _fly; Ui::RpWidget _layer; QRect _area; From 1207e84dcb4fdccb836c93f22ef85e031cea98a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Aug 2023 17:59:13 +0200 Subject: [PATCH 021/104] Add reaction menu to story context menu. --- .../history_view_reactions_selector.cpp | 79 ++++++++++++------- .../history_view_reactions_selector.h | 14 +++- .../stories/media_stories_controller.cpp | 7 ++ .../media/stories/media_stories_controller.h | 7 ++ .../media/stories/media_stories_reactions.cpp | 34 ++++++++ .../media/stories/media_stories_reactions.h | 7 ++ .../media/stories/media_stories_view.cpp | 7 ++ .../media/stories/media_stories_view.h | 13 +++ .../SourceFiles/media/view/media_view.style | 51 +++++------- .../media/view/media_view_overlay_widget.cpp | 34 +++++--- 10 files changed, 182 insertions(+), 71 deletions(-) diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 0f2874c4e..e24577962 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -1036,21 +1036,65 @@ AttachSelectorResult AttachSelectorToMenu( Fn chosen, Fn showPremiumPromo, IconFactory iconFactory) { - auto reactions = Data::LookupPossibleReactions(item); + const auto result = AttachSelectorToMenu( + menu, + desiredPosition, + st::reactPanelEmojiPan, + controller->uiShow(), + Data::LookupPossibleReactions(item), + std::move(iconFactory)); + if (!result) { + return result.error(); + } + const auto selector = *result; + const auto itemId = item->fullId(); + + selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) { + menu->hideMenu(); + reaction.context = itemId; + chosen(std::move(reaction)); + }, selector->lifetime()); + + selector->premiumPromoChosen() | rpl::start_with_next([=] { + menu->hideMenu(); + showPremiumPromo(itemId); + }, selector->lifetime()); + + const auto weak = base::make_weak(controller); + controller->enableGifPauseReason( + Window::GifPauseReason::MediaPreview); + QObject::connect(menu.get(), &QObject::destroyed, [weak] { + if (const auto strong = weak.get()) { + strong->disableGifPauseReason( + Window::GifPauseReason::MediaPreview); + } + }); + + return AttachSelectorResult::Attached; +} + +auto AttachSelectorToMenu( + not_null menu, + QPoint desiredPosition, + const style::EmojiPan &st, + std::shared_ptr show, + const Data::PossibleItemReactionsRef &reactions, + IconFactory iconFactory) +-> base::expected, AttachSelectorResult> { if (reactions.recent.empty() && !reactions.morePremiumAvailable) { - return AttachSelectorResult::Skipped; + return base::make_unexpected(AttachSelectorResult::Skipped); } const auto withSearch = reactions.customAllowed; const auto selector = Ui::CreateChild( menu.get(), - st::reactPanelEmojiPan, - controller->uiShow(), + st, + std::move(show), std::move(reactions), std::move(iconFactory), [=](bool fast) { menu->hideMenu(fast); }, false); // child if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { - return AttachSelectorResult::Failed; + return base::make_unexpected(AttachSelectorResult::Failed); } if (withSearch) { Ui::Platform::FixPopupMenuNativeEmojiPopup(menu); @@ -1067,19 +1111,6 @@ AttachSelectorResult AttachSelectorToMenu( selector->initGeometry(selectorInnerTop); selector->show(); - const auto itemId = item->fullId(); - - selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) { - menu->hideMenu(); - reaction.context = itemId; - chosen(std::move(reaction)); - }, selector->lifetime()); - - selector->premiumPromoChosen() | rpl::start_with_next([=] { - menu->hideMenu(); - showPremiumPromo(itemId); - }, selector->lifetime()); - const auto correctTop = selector->y(); menu->showStateValue( ) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) { @@ -1100,17 +1131,7 @@ AttachSelectorResult AttachSelectorToMenu( state.toggling); }, selector->lifetime()); - const auto weak = base::make_weak(controller); - controller->enableGifPauseReason( - Window::GifPauseReason::MediaPreview); - QObject::connect(menu.get(), &QObject::destroyed, [weak] { - if (const auto strong = weak.get()) { - strong->disableGifPauseReason( - Window::GifPauseReason::MediaPreview); - } - }); - - return AttachSelectorResult::Attached; + return selector; } } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 270644bdc..8d3b34a03 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "history/view/reactions/history_view_reactions_strip.h" -#include "data/data_message_reactions.h" +#include "base/expected.h" #include "base/unique_qptr.h" +#include "data/data_message_reactions.h" +#include "history/view/reactions/history_view_reactions_strip.h" #include "ui/effects/animation_value.h" #include "ui/effects/round_area_with_shadow.h" #include "ui/rp_widget.h" @@ -210,4 +211,13 @@ AttachSelectorResult AttachSelectorToMenu( Fn showPremiumPromo, IconFactory iconFactory); +[[nodiscard]] auto AttachSelectorToMenu( + not_null menu, + QPoint desiredPosition, + const style::EmojiPan &st, + std::shared_ptr show, + const Data::PossibleItemReactionsRef &reactions, + IconFactory iconFactory +) -> base::expected, AttachSelectorResult>; + } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index b12e56585..0e776caaa 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1552,6 +1552,13 @@ void Controller::setupStealthMode() { SetupStealthMode(uiShow()); } +auto Controller::attachReactionsToMenu( + not_null menu, + QPoint desiredPosition) +-> AttachStripResult { + return _reactions->attachToMenu(menu, desiredPosition); +} + rpl::lifetime &Controller::lifetime() { return _lifetime; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index d7a9c37ff..f932c40a6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -32,11 +32,13 @@ class DocumentMedia; namespace HistoryView::Reactions { class CachedIconFactory; struct ChosenReaction; +enum class AttachSelectorResult; } // namespace HistoryView::Reactions namespace Ui { class RpWidget; class BoxContent; +class PopupMenu; } // namespace Ui namespace Ui::Toast { @@ -167,6 +169,11 @@ public: [[nodiscard]] bool allowStealthMode() const; void setupStealthMode(); + using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; + [[nodiscard]] AttachStripResult attachReactionsToMenu( + not_null menu, + QPoint desiredPosition); + [[nodiscard]] rpl::lifetime &lifetime(); private: diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index e9162d891..02b42d593 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -14,12 +14,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_message_reactions.h" +#include "data/data_peer.h" #include "data/data_session.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" #include "ui/effects/emoji_fly_animation.h" #include "ui/effects/reaction_fly_animation.h" +#include "ui/widgets/popup_menu.h" #include "ui/animated_icon.h" #include "ui/painter.h" #include "styles/style_chat_helpers.h" @@ -389,6 +391,38 @@ void Reactions::attachToReactionButton(not_null button) { _panel->attachToReactionButton(button); } +auto Reactions::attachToMenu( + not_null menu, + QPoint desiredPosition) +-> AttachStripResult { + using namespace HistoryView::Reactions; + + const auto story = _controller->story(); + if (!story || story->peer()->isSelf()) { + return AttachStripResult::Skipped; + } + + const auto show = _controller->uiShow(); + const auto result = AttachSelectorToMenu( + menu, + desiredPosition, + st::storiesReactionsPan, + show, + LookupPossibleReactions(&show->session()), + _controller->cachedReactionIconFactory().createMethod()); + if (!result) { + return result.error(); + } + const auto selector = *result; + + selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) { + menu->hideMenu(); + animateAndProcess({ reaction, ReactionsMode::Reaction }); + }, selector->lifetime()); + + return AttachSelectorResult::Attached; +} + Data::ReactionId Reactions::liked() const { return _liked.current(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index 5a16943a7..fbcf34b3c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -20,6 +20,7 @@ class Story; namespace HistoryView::Reactions { class Selector; struct ChosenReaction; +enum class AttachSelectorResult; } // namespace HistoryView::Reactions namespace Ui { @@ -27,6 +28,7 @@ class RpWidget; struct ReactionFlyAnimationArgs; struct ReactionFlyCenter; class EmojiFlyAnimation; +class PopupMenu; } // namespace Ui namespace Media::Stories { @@ -69,6 +71,11 @@ public: rpl::producer hasSendText); void attachToReactionButton(not_null button); + using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; + [[nodiscard]] AttachStripResult attachToMenu( + not_null menu, + QPoint desiredPosition); + private: class Panel; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 1aa545726..41390c3c4 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -123,6 +123,13 @@ void View::setupStealthMode() { _controller->setupStealthMode(); } +auto View::attachReactionsToMenu( + not_null menu, + QPoint desiredPosition) +-> AttachStripResult { + return _controller->attachReactionsToMenu(menu, desiredPosition); +} + SiblingView View::sibling(SiblingType type) const { return _controller->sibling(type); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index e70b0a6c9..730488253 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -17,6 +17,14 @@ namespace Media::Player { struct TrackState; } // namespace Media::Player +namespace HistoryView::Reactions { +enum class AttachSelectorResult; +} // namespace HistoryView::Reactions + +namespace Ui { +class PopupMenu; +} // namespace Ui + namespace Media::Stories { class Delegate; @@ -95,6 +103,11 @@ public: [[nodiscard]] bool allowStealthMode() const; void setupStealthMode(); + using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; + [[nodiscard]] AttachStripResult attachReactionsToMenu( + not_null menu, + QPoint desiredPosition); + [[nodiscard]] rpl::lifetime &lifetime(); private: diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index db01d30fa..a2426a990 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -145,29 +145,30 @@ mediaviewFileIconSize: 80px; mediaviewFileLink: defaultLinkButton; mediaviewMenuSeparator: MenuSeparator(defaultMenuSeparator) { - fg: mediaviewMenuFg; + fg: groupCallMenuBgOver; } mediaviewMenu: Menu(menuWithIcons) { - itemBg: mediaviewMenuBg; - itemBgOver: mediaviewMenuBgOver; - itemFg: mediaviewMenuFg; - itemFgOver: mediaviewMenuFg; - itemFgDisabled: mediaviewMenuFg; - itemFgShortcut: mediaviewMenuFg; - itemFgShortcutOver: mediaviewMenuFg; - itemFgShortcutDisabled: mediaviewMenuFg; + itemBg: groupCallMenuBg; + itemBgOver: groupCallMenuBgOver; + itemFg: groupCallMembersFg; + itemFgOver: groupCallMembersFg; + itemFgDisabled: groupCallMemberNotJoinedStatus; + itemFgShortcut: groupCallMemberNotJoinedStatus; + itemFgShortcutOver: groupCallMemberNotJoinedStatus; + itemFgShortcutDisabled: groupCallMemberNotJoinedStatus; separator: mediaviewMenuSeparator; + arrow: icon {{ "menu/submenu_arrow", groupCallMemberNotJoinedStatus }}; ripple: RippleAnimation(defaultRippleAnimation) { - color: mediaviewMenuBgRipple; + color: groupCallMenuBgRipple; } } mediaviewMenuShadow: Shadow(defaultEmptyShadow) { - fallback: mediaviewMenuBg; + fallback: groupCallMenuBg; } mediaviewPanelAnimation: PanelAnimation(defaultPanelAnimation) { - fadeBg: mediaviewMenuBg; + fadeBg: groupCallMenuBg; shadow: mediaviewMenuShadow; } mediaviewPopupMenu: PopupMenu(defaultPopupMenu) { @@ -178,7 +179,7 @@ mediaviewPopupMenu: PopupMenu(defaultPopupMenu) { mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) { menu: mediaviewMenu; wrap: InnerDropdown(defaultInnerDropdown) { - bg: mediaviewMenuBg; + bg: groupCallMenuBg; animation: mediaviewPanelAnimation; scrollPadding: margins(0px, 8px, 0px, 8px); shadow: mediaviewMenuShadow; @@ -312,7 +313,7 @@ mediaviewSpeedMenu: MediaSpeedMenu(mediaPlayerSpeedMenu) { dropdown: DropdownMenu(mediaviewDropdownMenu) { menu: Menu(mediaviewMenu) { separator: MenuSeparator(mediaviewMenuSeparator) { - fg: mediaviewMenuBgOver; + fg: groupCallMenuBgOver; padding: margins(0px, 4px, 0px, 4px); width: 6px; } @@ -478,9 +479,7 @@ storiesRemoveSet: IconButton(stickerPanRemoveSet) { iconOver: icon {{ "simple_close", storiesComposeGrayIcon }}; ripple: storiesComposeRippleLight; } -storiesMenuSeparator: MenuSeparator(defaultMenuSeparator) { - fg: groupCallMenuBgOver; -} +storiesMenuSeparator: mediaviewMenuSeparator; storiesMenu: Menu(defaultMenu) { itemBg: groupCallMenuBg; itemBgOver: groupCallMenuBgOver; @@ -498,22 +497,14 @@ storiesMenu: Menu(defaultMenu) { color: groupCallMenuBgRipple; } } -storiesMenuShadow: Shadow(defaultEmptyShadow) { - fallback: groupCallMenuBg; -} -storiesMenuAnimation: PanelAnimation(defaultPanelAnimation) { - fadeBg: groupCallMenuBg; - shadow: storiesMenuShadow; -} +storiesMenuShadow: mediaviewMenuShadow; +storiesMenuAnimation: mediaviewPanelAnimation; storiesPopupMenu: PopupMenu(defaultPopupMenu) { shadow: storiesMenuShadow; menu: storiesMenu; animation: storiesMenuAnimation; } -storiesMenuWithIcons: Menu(storiesMenu) { - itemIconPosition: point(15px, 5px); - itemPadding: margins(54px, 8px, 17px, 8px); -} +storiesMenuWithIcons: mediaviewMenu; storiesPopupMenuWithIcons: PopupMenu(storiesPopupMenu) { scrollPadding: margins(0px, 5px, 0px, 5px); menu: storiesMenuWithIcons; @@ -779,8 +770,8 @@ storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) { maxHeight: 320px; menu: Menu(storiesMenuWithIcons) { itemPadding: margins(54px, 8px, 17px, 8px); - widthMin: 215px; - widthMax: 250px; + widthMin: 240px; + widthMax: 240px; } radius: 7px; } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 01e6ba1fc..277538cad 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -56,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_helpers.h" #include "history/view/media/history_view_media.h" #include "history/view/reactions/history_view_reactions_strip.h" +#include "history/view/reactions/history_view_reactions_selector.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_stories.h" @@ -5849,20 +5850,33 @@ bool OverlayWidget::handleContextMenu(std::optional position) { const style::icon *icon) { _menu->addAction(text, std::move(handler), icon); }); + if (_menu->empty()) { _menu = nullptr; - } else { + return true; + } + if (_stories) { + _stories->menuShown(true); + } + _menu->setDestroyedCallback(crl::guard(_widget, [=] { if (_stories) { - _stories->menuShown(true); + _stories->menuShown(false); } - _menu->setDestroyedCallback(crl::guard(_widget, [=] { - if (_stories) { - _stories->menuShown(false); - } - activateControls(); - _receiveMouse = false; - InvokeQueued(_widget, [=] { receiveMouse(); }); - })); + activateControls(); + _receiveMouse = false; + InvokeQueued(_widget, [=] { receiveMouse(); }); + })); + + using HistoryView::Reactions::AttachSelectorResult; + const auto attached = _stories + ? _stories->attachReactionsToMenu(_menu.get(), QCursor::pos()) + : AttachSelectorResult::Skipped; + if (attached == AttachSelectorResult::Failed) { + _menu = nullptr; + return true; + } else if (attached == AttachSelectorResult::Attached) { + _menu->popupPrepared(); + } else { _menu->popup(QCursor::pos()); } activateControls(); From dbe7f4288135eeb384024277503a7e0a7fd5413a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 9 Aug 2023 10:35:11 +0200 Subject: [PATCH 022/104] Update API scheme on layer 161. --- Telegram/SourceFiles/data/data_stories.cpp | 3 ++- Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index d795b41e3..420177a1e 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1266,8 +1266,9 @@ void Stories::loadViewsSlice( const auto api = &_owner->session().api(); const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage; api->request(_viewsRequestId).cancel(); + using Flag = MTPstories_GetStoryViewsList::Flag; _viewsRequestId = api->request(MTPstories_GetStoryViewsList( - MTP_flags(0), + MTP_flags(Flag::f_reactions_first), MTPstring(), // q MTP_int(id), MTP_string(offset), diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index a4f1092f9..bced7ee92 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -2112,7 +2112,7 @@ stories.toggleAllStoriesHidden#7c2557c4 hidden:Bool = Bool; stories.getAllReadUserStories#729c562c = Updates; stories.readStories#edc5105b user_id:InputUser max_id:int = Vector; stories.incrementStoryViews#22126127 user_id:InputUser id:Vector = Bool; -stories.getStoryViewsList#f95f61a4 flags:# just_contacts:flags.0?true q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; +stories.getStoryViewsList#f95f61a4 flags:# just_contacts:flags.0?true reactions_first:flags.2?true q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; stories.getStoriesViews#9a75d6a6 id:Vector = stories.StoryViews; stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink; stories.report#c95be06a user_id:InputUser id:Vector reason:ReportReason message:string = Bool; From 88c7b16b44b8131696b4d30906829387d031b847 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 9 Aug 2023 10:35:37 +0200 Subject: [PATCH 023/104] Fix reaction emoji disappearing. --- .../SourceFiles/media/stories/media_stories_recent_views.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 4bfe93d64..565cf5995 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -455,6 +455,7 @@ void RecentViews::subscribeToMenuUserpicsLoading( entry.action->setData({ .text = peer->name(), .date = entry.date, + .customEntityData = entry.customEntityData, .userpic = std::move(userpic), .callback = entry.callback, }); From ae26c781c11b8155cba8f3601668fc6fd3364dbe Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Aug 2023 22:27:11 +0200 Subject: [PATCH 024/104] Fix build with Xcode. --- Telegram/SourceFiles/media/stories/media_stories_stealth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp index 04a78c7f2..404ff1aea 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp @@ -138,7 +138,7 @@ struct Feature { return { .icon = st::storiesStealthFeaturePastIcon, .title = tr::lng_stealth_mode_past_title(tr::now), - .about = tr::lng_stealth_mode_past_about(tr::now), + .about = { tr::lng_stealth_mode_past_about(tr::now) }, }; } @@ -146,7 +146,7 @@ struct Feature { return { .icon = st::storiesStealthFeatureNextIcon, .title = tr::lng_stealth_mode_next_title(tr::now), - .about = tr::lng_stealth_mode_next_about(tr::now), + .about = { tr::lng_stealth_mode_next_about(tr::now) }, }; } From de4152496f52690e83a4295d5fc1916ba69ecff8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 16:20:38 +0200 Subject: [PATCH 025/104] Add archive settings to context menu. --- Telegram/Resources/langs/lang.strings | 7 ++ .../SourceFiles/api/api_global_privacy.cpp | 19 +++- Telegram/SourceFiles/api/api_global_privacy.h | 7 +- .../settings/settings_advanced.cpp | 93 ++++++++++++++++++ .../SourceFiles/settings/settings_advanced.h | 11 +++ .../settings/settings_privacy_security.cpp | 94 +++++++++---------- .../settings/settings_privacy_security.h | 4 + .../SourceFiles/window/window_main_menu.cpp | 10 +- .../SourceFiles/window/window_peer_menu.cpp | 7 ++ 9 files changed, 199 insertions(+), 53 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3b86a7dda..18396ecb4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -615,9 +615,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_security" = "Security"; "lng_settings_passcode_title" = "Local passcode"; "lng_settings_sessions_title" = "Active sessions"; +"lng_settings_archive_title" = "Archive Settings"; "lng_settings_new_unknown" = "New chats from unknown users"; "lng_settings_auto_archive" = "Archive and Mute"; "lng_settings_auto_archive_about" = "Automatically archive and mute new chats, groups and channels from non-contacts."; +"lng_settings_unmuted_chats" = "Unmuted chats"; +"lng_settings_always_in_archive" = "Always keep archived"; +"lng_settings_unmuted_chats_about" = "Keep archived chats in the Archive even if they are unmuted and get a new message."; +"lng_settings_chats_from_folders" = "Chats from folders"; +"lng_settings_chats_from_folders_about" = "Keep archived chats from folders in the Archive even if they are unmuted and get a new message."; "lng_settings_destroy_title" = "Delete my account"; "lng_settings_version_info" = "Version and updates"; "lng_settings_system_integration" = "System integration"; @@ -2278,6 +2284,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_archive_to_menu" = "Move to main menu"; "lng_context_archive_to_list" = "Move to chats list"; "lng_context_archive_to_menu_info" = "Archive moved to the main menu!\nYou can return it from the context menu of the archive button."; +"lng_context_archive_settings" = "Archive settings"; "lng_context_mute" = "Mute"; "lng_context_unmute" = "Unmute"; diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 89e1d57da..43a60e9a3 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -84,19 +84,29 @@ void GlobalPrivacy::dismissArchiveAndMuteSuggestion() { u"AUTOARCHIVE_POPULAR"_q); } -void GlobalPrivacy::update(bool archiveAndMute) { +void GlobalPrivacy::updateArchiveAndMute(bool value) { + update(value, unarchiveOnNewMessageCurrent()); +} + +void GlobalPrivacy::updateUnarchiveOnNewMessage( + UnarchiveOnNewMessage value) { + update(archiveAndMuteCurrent(), value); +} + +void GlobalPrivacy::update( + bool archiveAndMute, + UnarchiveOnNewMessage unarchiveOnNewMessage) { using Flag = MTPDglobalPrivacySettings::Flag; - const auto unarchive = unarchiveOnNewMessageCurrent(); _api.request(_requestId).cancel(); const auto flags = Flag() | (archiveAndMute ? Flag::f_archive_and_mute_new_noncontact_peers : Flag()) - | (unarchive == UnarchiveOnNewMessage::AnyUnmuted + | (unarchiveOnNewMessage == UnarchiveOnNewMessage::AnyUnmuted ? Flag::f_keep_archived_unmuted : Flag()) - | (unarchive != UnarchiveOnNewMessage::None + | (unarchiveOnNewMessage != UnarchiveOnNewMessage::None ? Flag::f_keep_archived_folders : Flag()); _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( @@ -108,6 +118,7 @@ void GlobalPrivacy::update(bool archiveAndMute) { _requestId = 0; }).send(); _archiveAndMute = archiveAndMute; + _unarchiveOnNewMessage = unarchiveOnNewMessage; } void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index f569951ca..9e4b8e121 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -28,7 +28,8 @@ public: explicit GlobalPrivacy(not_null api); void reload(Fn callback = nullptr); - void update(bool archiveAndMute); + void updateArchiveAndMute(bool value); + void updateUnarchiveOnNewMessage(UnarchiveOnNewMessage value); [[nodiscard]] bool archiveAndMuteCurrent() const; [[nodiscard]] rpl::producer archiveAndMute() const; @@ -43,6 +44,10 @@ public: private: void apply(const MTPGlobalPrivacySettings &data); + void update( + bool archiveAndMute, + UnarchiveOnNewMessage unarchiveOnNewMessage); + const not_null _session; MTP::Sender _api; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 1829f92a2..096bcbbdd 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -7,16 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_advanced.h" +#include "api/api_global_privacy.h" +#include "apiwrap.h" #include "settings/settings_common.h" #include "settings/settings_chat.h" #include "settings/settings_experimental.h" #include "settings/settings_power_saving.h" +#include "settings/settings_privacy_security.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/labels.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/gl/gl_detection.h" +#include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/format_values.h" #include "ui/boxes/single_choice_box.h" @@ -43,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "mtproto/facade.h" #include "styles/style_settings.h" +#include "styles/style_layers.h" #ifdef Q_OS_MAC #include "base/platform/mac/base_confirm_quit.h" @@ -710,6 +715,94 @@ void SetupAnimations( }); } +void ArchiveSettingsBox( + not_null box, + not_null controller) { + box->setTitle(tr::lng_settings_archive_title()); + box->setWidth(st::boxWideWidth); + + box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); + + PreloadArchiveSettings(&controller->session()); + + struct State { + Ui::SlideWrap *foldersWrap = nullptr; + Ui::SettingsButton *folders = nullptr; + }; + const auto state = box->lifetime().make_state(); + const auto privacy = &controller->session().api().globalPrivacy(); + + const auto container = box->verticalLayout(); + AddSkip(container); + AddSubsectionTitle(container, tr::lng_settings_unmuted_chats()); + + using Unarchive = Api::UnarchiveOnNewMessage; + AddButton( + container, + tr::lng_settings_always_in_archive(), + st::settingsButtonNoIcon + )->toggleOn(privacy->unarchiveOnNewMessage( + ) | rpl::map( + rpl::mappers::_1 == Unarchive::None + ))->toggledChanges( + ) | rpl::filter([=](bool toggled) { + const auto current = privacy->unarchiveOnNewMessageCurrent(); + return toggled != (current == Unarchive::None); + }) | rpl::start_with_next([=](bool toggled) { + privacy->updateUnarchiveOnNewMessage(toggled + ? Unarchive::None + : state->folders->toggled() + ? Unarchive::NotInFoldersUnmuted + : Unarchive::AnyUnmuted); + state->foldersWrap->toggle(!toggled, anim::type::normal); + }, container->lifetime()); + + AddSkip(container); + AddDividerText(container, tr::lng_settings_unmuted_chats_about()); + + state->foldersWrap = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto inner = state->foldersWrap->entity(); + AddSkip(inner); + AddSubsectionTitle(inner, tr::lng_settings_chats_from_folders()); + + state->folders = AddButton( + inner, + tr::lng_settings_always_in_archive(), + st::settingsButtonNoIcon + )->toggleOn(privacy->unarchiveOnNewMessage( + ) | rpl::map( + rpl::mappers::_1 != Unarchive::AnyUnmuted + )); + state->folders->toggledChanges( + ) | rpl::filter([=](bool toggled) { + const auto current = privacy->unarchiveOnNewMessageCurrent(); + return toggled != (current != Unarchive::AnyUnmuted); + }) | rpl::start_with_next([=](bool toggled) { + const auto current = privacy->unarchiveOnNewMessageCurrent(); + privacy->updateUnarchiveOnNewMessage(!toggled + ? Unarchive::AnyUnmuted + : (current == Unarchive::AnyUnmuted) + ? Unarchive::NotInFoldersUnmuted + : current); + }, inner->lifetime()); + + AddSkip(inner); + AddDividerText(inner, tr::lng_settings_chats_from_folders_about()); + + state->foldersWrap->toggle( + privacy->unarchiveOnNewMessageCurrent() != Unarchive::None, + anim::type::instant); + + SetupArchiveAndMute(controller, box->verticalLayout()); +} + +void PreloadArchiveSettings(not_null<::Main::Session*> session) { + session->api().globalPrivacy().reload(); +} + void SetupHardwareAcceleration(not_null container) { const auto settings = &Core::App().settings(); AddButton( diff --git a/Telegram/SourceFiles/settings/settings_advanced.h b/Telegram/SourceFiles/settings/settings_advanced.h index 27afff623..21aa2014f 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.h +++ b/Telegram/SourceFiles/settings/settings_advanced.h @@ -11,10 +11,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Main { class Account; +class Session; } // namespace Main +namespace Ui { +class GenericBox; +} // namespace Ui + namespace Window { class Controller; +class SessionController; } // namespace Window namespace Settings { @@ -37,6 +43,11 @@ void SetupAnimations( not_null window, not_null container); +void ArchiveSettingsBox( + not_null box, + not_null controller); +void PreloadArchiveSettings(not_null<::Main::Session*> session); + class Advanced : public Section { public: Advanced( diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index d9a1a97c3..63df59c3d 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -311,53 +311,6 @@ void SetupPrivacy( AddDivider(container); } -void SetupArchiveAndMute( - not_null controller, - not_null container) { - using namespace rpl::mappers; - - const auto wrap = container->add( - object_ptr>( - container, - object_ptr(container))); - const auto inner = wrap->entity(); - - AddSkip(inner); - AddSubsectionTitle(inner, tr::lng_settings_new_unknown()); - - const auto session = &controller->session(); - - const auto privacy = &session->api().globalPrivacy(); - privacy->reload(); - AddButton( - inner, - tr::lng_settings_auto_archive(), - st::settingsButtonNoIcon - )->toggleOn( - privacy->archiveAndMute() - )->toggledChanges( - ) | rpl::filter([=](bool toggled) { - return toggled != privacy->archiveAndMuteCurrent(); - }) | rpl::start_with_next([=](bool toggled) { - privacy->update(toggled); - }, container->lifetime()); - - AddSkip(inner); - AddDividerText(inner, tr::lng_settings_auto_archive_about()); - - auto shown = rpl::single( - false - ) | rpl::then(session->api().globalPrivacy().showArchiveAndMute( - ) | rpl::filter(_1) | rpl::take(1)); - auto premium = Data::AmPremiumValue(&controller->session()); - - using namespace rpl::mappers; - wrap->toggleOn(rpl::combine( - std::move(shown), - std::move(premium), - _1 || _2)); -} - void SetupLocalPasscode( not_null controller, not_null container, @@ -838,6 +791,53 @@ void AddPrivacyButton( }); } +void SetupArchiveAndMute( + not_null controller, + not_null container) { + using namespace rpl::mappers; + + const auto wrap = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto inner = wrap->entity(); + + AddSkip(inner); + AddSubsectionTitle(inner, tr::lng_settings_new_unknown()); + + const auto session = &controller->session(); + + const auto privacy = &session->api().globalPrivacy(); + privacy->reload(); + AddButton( + inner, + tr::lng_settings_auto_archive(), + st::settingsButtonNoIcon + )->toggleOn( + privacy->archiveAndMute() + )->toggledChanges( + ) | rpl::filter([=](bool toggled) { + return toggled != privacy->archiveAndMuteCurrent(); + }) | rpl::start_with_next([=](bool toggled) { + privacy->updateArchiveAndMute(toggled); + }, container->lifetime()); + + AddSkip(inner); + AddDividerText(inner, tr::lng_settings_auto_archive_about()); + + auto shown = rpl::single( + false + ) | rpl::then(session->api().globalPrivacy().showArchiveAndMute( + ) | rpl::filter(_1) | rpl::take(1)); + auto premium = Data::AmPremiumValue(&controller->session()); + + using namespace rpl::mappers; + wrap->toggleOn(rpl::combine( + std::move(shown), + std::move(premium), + _1 || _2)); +} + PrivacySecurity::PrivacySecurity( QWidget *parent, not_null controller) diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.h b/Telegram/SourceFiles/settings/settings_privacy_security.h index 350d6f1b5..83dd827ee 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.h +++ b/Telegram/SourceFiles/settings/settings_privacy_security.h @@ -35,6 +35,10 @@ void AddPrivacyButton( Fn()> controllerFactory, const style::SettingsButton *stOverride = nullptr); +void SetupArchiveAndMute( + not_null controller, + not_null container); + class PrivacySecurity : public Section { public: PrivacySecurity( diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index ba24fba90..52c3e67e3 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "storage/storage_account.h" #include "support/support_templates.h" +#include "settings/settings_advanced.h" #include "settings/settings_common.h" #include "settings/settings_calls.h" #include "settings/settings_information.h" @@ -63,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_stories.h" #include "mainwidget.h" +#include "styles/style_chat.h" // popupMenuExpandedSeparator #include "styles/style_window.h" #include "styles/style_widgets.h" #include "styles/style_dialogs.h" @@ -602,7 +604,7 @@ void MainMenu::setupArchive() { } _contextMenu = base::make_unique_q( this, - st::popupMenuWithIcons); + st::popupMenuExpandedSeparator); const auto addAction = PeerMenuCallback([&]( PeerMenuCallback::Args a) { return _contextMenu->addAction( @@ -626,6 +628,12 @@ void MainMenu::setupArchive() { [f = folder()] { return f->chatsList(); }, addAction); + _contextMenu->addSeparator(); + Settings::PreloadArchiveSettings(&controller->session()); + addAction(tr::lng_context_archive_settings(tr::now), [=] { + controller->show(Box(Settings::ArchiveSettingsBox, controller)); + }, &st::menuIconManage); + _contextMenu->popup(QCursor::pos()); }, button->lifetime()); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 9098b9112..92025ae40 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_adaptive.h" // Adaptive::isThreeColumn #include "window/window_session_controller.h" #include "window/window_controller.h" +#include "settings/settings_advanced.h" #include "support/support_helper.h" #include "info/info_memento.h" #include "info/info_controller.h" @@ -1276,6 +1277,12 @@ void Filler::fillArchiveActions() { controller, [folder = _folder] { return folder->chatsList(); }, _addAction); + + _addAction({ .isSeparator = true }); + Settings::PreloadArchiveSettings(&controller->session()); + _addAction(tr::lng_context_archive_settings(tr::now), [=] { + controller->show(Box(Settings::ArchiveSettingsBox, controller)); + }, &st::menuIconManage); } } // namespace From 027e6624783eccf76647a1e704e74b9da8c71ae6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 17:48:38 +0200 Subject: [PATCH 026/104] Fix story links opening viewer in the background. --- Telegram/SourceFiles/core/local_url_handlers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index d18b842c0..61d31e64d 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -412,6 +412,7 @@ bool ResolveUsernameOrPhone( } const auto myContext = context.value(); using Navigation = Window::SessionNavigation; + controller->window().activate(); controller->showPeerByLink(Navigation::PeerByLinkInfo{ .usernameOrId = domain, .phone = phone, @@ -447,7 +448,6 @@ bool ResolveUsernameOrPhone( : std::nullopt), .clickFromMessageId = myContext.itemId, }); - controller->window().activate(); return true; } From d52475666d0167d891128312aaf5e5e0a7257e39 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 18:12:35 +0200 Subject: [PATCH 027/104] Suggest group-with-hidden-members admins in mentions. --- .../SourceFiles/api/api_chat_participants.cpp | 2 ++ .../boxes/peers/edit_participants_box.cpp | 5 ++++ .../chat_helpers/field_autocomplete.cpp | 24 +++++++++++++++---- Telegram/SourceFiles/data/data_channel.h | 1 + .../SourceFiles/history/history_widget.cpp | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp index aeb7bd0a6..0ee748f2b 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.cpp +++ b/Telegram/SourceFiles/api/api_chat_participants.cpp @@ -439,6 +439,7 @@ void ChatParticipants::requestAdmins(not_null channel) { MTP_int(channel->session().serverConfig().chatSizeMax), MTP_long(participantsHash) )).done([=](const MTPchannels_ChannelParticipants &result) { + channel->mgInfo->adminsLoaded = true; _adminsRequests.remove(channel); result.match([&](const MTPDchannels_channelParticipants &data) { channel->owner().processUsers(data.vusers()); @@ -448,6 +449,7 @@ void ChatParticipants::requestAdmins(not_null channel) { "channels.channelParticipantsNotModified received!")); }); }).fail([=] { + channel->mgInfo->adminsLoaded = true; _adminsRequests.remove(channel); }).send(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index e1d09efec..4ec1fd6be 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -1505,6 +1505,11 @@ void ParticipantsBoxController::loadMoreRows() { LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); + if (_offset > 0 && _role == Role::Admins && channel->isMegagroup()) { + if (channel->mgInfo->admins.empty() && channel->mgInfo->adminsLoaded) { + channel->mgInfo->adminsLoaded = false; + } + } if (!firstLoad && !added) { _allLoaded = true; } diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 7f5c7a52e..4285247a1 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -422,7 +422,9 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { if (_chat) { maxListSize += (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size()); } else if (_channel && _channel->isMegagroup()) { - if (!_channel->lastParticipantsRequestNeeded()) { + if (!_channel->canViewMembers()) { + maxListSize += _channel->mgInfo->admins.size(); + } else if (!_channel->lastParticipantsRequestNeeded()) { maxListSize += _channel->mgInfo->lastParticipants.size(); } } @@ -488,10 +490,22 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { --i; mrows.push_back({ i->second }); } - } else if (_channel - && _channel->isMegagroup() - && _channel->canViewMembers()) { - if (_channel->lastParticipantsRequestNeeded()) { + } else if (_channel && _channel->isMegagroup()) { + if (!_channel->canViewMembers()) { + if (!_channel->mgInfo->adminsLoaded) { + _channel->session().api().chatParticipants().requestAdmins(_channel); + } else { + mrows.reserve(mrows.size() + _channel->mgInfo->admins.size()); + for (const auto [userId, rank] : _channel->mgInfo->admins) { + if (const auto user = _channel->owner().userLoaded(userId)) { + if (user->isInaccessible()) continue; + if (!listAllSuggestions && filterNotPassedByName(user)) continue; + if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; + mrows.push_back({ user }); + } + } + } + } else if (_channel->lastParticipantsRequestNeeded()) { _channel->session().api().chatParticipants().requestLast( _channel); } else { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 9817da915..6c05d5c88 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -116,6 +116,7 @@ public: QString creatorRank; int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other bool joinedMessageFound = false; + bool adminsLoaded = false; StickerSetIdentifier stickerSet; enum LastParticipantsStatus { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d69117441..6c71cd83a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7495,7 +7495,7 @@ void HistoryWidget::handlePeerUpdate() { if (!channel->mgInfo->botStatus) { session().api().chatParticipants().requestBots(channel); } - if (channel->mgInfo->admins.empty()) { + if (!channel->mgInfo->adminsLoaded) { session().api().chatParticipants().requestAdmins(channel); } } From f1ab712f077ec73c42812995056b48c7f9a16ec8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 20:22:50 +0200 Subject: [PATCH 028/104] Fix outdated bar closing. --- Telegram/SourceFiles/ui/controls/window_outdated_bar.cpp | 3 +++ Telegram/SourceFiles/window/main_window.cpp | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/window_outdated_bar.cpp b/Telegram/SourceFiles/ui/controls/window_outdated_bar.cpp index 538e3fcda..2ad527ce2 100644 --- a/Telegram/SourceFiles/ui/controls/window_outdated_bar.cpp +++ b/Telegram/SourceFiles/ui/controls/window_outdated_bar.cpp @@ -174,6 +174,9 @@ object_ptr CreateOutdatedBar( Closed(workingPath); }, wrap->lifetime()); + wrap->entity()->resizeToWidth(st::windowMinWidth); + wrap->show(anim::type::instant); + return result; #else // DESKTOP_APP_SPECIAL_TARGET return { nullptr }; diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 07c2d8a4d..c1d17fa74 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -375,9 +375,7 @@ MainWindow::MainWindow(not_null controller) if (_outdated) { _outdated->heightValue( - ) | rpl::filter([=] { - return window()->windowHandle() != nullptr; - }) | rpl::start_with_next([=](int height) { + ) | rpl::start_with_next([=](int height) { if (!height) { crl::on_main(this, [=] { _outdated.destroy(); }); } From 94f842a81f8181decec275c6c49f9b0608400274 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Aug 2023 21:55:45 +0200 Subject: [PATCH 029/104] Fix Shift+F10 context menu in media viewer. Fixes #26595. --- .../SourceFiles/media/view/media_view_overlay_widget.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 277538cad..350590a0d 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -492,6 +492,15 @@ OverlayWidget::OverlayWidget() return base::EventFilterResult::Cancel; } else if (type == QEvent::ThemeChange && Platform::IsLinux()) { _window->setWindowIcon(Window::CreateIcon(_session)); + } else if (type == QEvent::ContextMenu) { + const auto event = static_cast(e.get()); + const auto mouse = (event->reason() == QContextMenuEvent::Mouse); + const auto position = mouse + ? std::make_optional(event->pos()) + : std::nullopt; + if (handleContextMenu(position)) { + return base::EventFilterResult::Cancel; + } } return base::EventFilterResult::Continue; }); From 59546e87dc2f9d795724126c9972c8762a68efe0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 14 Aug 2023 14:04:22 +0200 Subject: [PATCH 030/104] Fix multi-monitor window adjust on Windows. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 18ac9868b..fd55e9b71 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 18ac9868bb24801c6d4bf7bd5d1e6197add2f4b4 +Subproject commit fd55e9b71b03282de682e9b8ac01f1b6801d25a9 From cc27b6c5c5ff06aba594ea4a1bd9b2d83ebab2f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 14 Aug 2023 14:04:33 +0200 Subject: [PATCH 031/104] Update icons in settings / manage layers. --- Telegram/Resources/icons/menu/antispam.png | Bin 0 -> 761 bytes Telegram/Resources/icons/menu/antispam@2x.png | Bin 0 -> 1427 bytes Telegram/Resources/icons/menu/antispam@3x.png | Bin 0 -> 2160 bytes .../Resources/icons/menu/bot_commands.png | Bin 0 -> 538 bytes .../Resources/icons/menu/bot_commands@2x.png | Bin 0 -> 821 bytes .../Resources/icons/menu/bot_commands@3x.png | Bin 0 -> 1153 bytes .../Resources/icons/menu/calls_receive.png | Bin 0 -> 773 bytes .../Resources/icons/menu/calls_receive@2x.png | Bin 0 -> 1466 bytes .../Resources/icons/menu/calls_receive@3x.png | Bin 0 -> 2124 bytes .../Resources/icons/menu/chat_discuss.png | Bin 0 -> 751 bytes .../Resources/icons/menu/chat_discuss@2x.png | Bin 0 -> 1370 bytes .../Resources/icons/menu/chat_discuss@3x.png | Bin 0 -> 2002 bytes Telegram/Resources/icons/menu/devices.png | Bin 0 -> 402 bytes Telegram/Resources/icons/menu/devices@2x.png | Bin 0 -> 595 bytes Telegram/Resources/icons/menu/devices@3x.png | Bin 0 -> 840 bytes Telegram/Resources/icons/menu/dock_bounce.png | Bin 0 -> 497 bytes .../Resources/icons/menu/dock_bounce@2x.png | Bin 0 -> 808 bytes .../Resources/icons/menu/dock_bounce@3x.png | Bin 0 -> 1152 bytes .../Resources/icons/menu/experimental.png | Bin 0 -> 582 bytes .../Resources/icons/menu/experimental@2x.png | Bin 0 -> 991 bytes .../Resources/icons/menu/experimental@3x.png | Bin 0 -> 1443 bytes Telegram/Resources/icons/menu/faq.png | Bin 0 -> 539 bytes Telegram/Resources/icons/menu/faq@2x.png | Bin 0 -> 1231 bytes Telegram/Resources/icons/menu/faq@3x.png | Bin 0 -> 1832 bytes Telegram/Resources/icons/menu/group_log.png | Bin 0 -> 539 bytes .../Resources/icons/menu/group_log@2x.png | Bin 0 -> 941 bytes .../Resources/icons/menu/group_log@3x.png | Bin 0 -> 1220 bytes .../Resources/icons/menu/group_reactions.png | Bin 0 -> 629 bytes .../icons/menu/group_reactions@2x.png | Bin 0 -> 1164 bytes .../icons/menu/group_reactions@3x.png | Bin 0 -> 1774 bytes .../Resources/icons/menu/groups_create.png | Bin 0 -> 717 bytes .../Resources/icons/menu/groups_create@2x.png | Bin 0 -> 1357 bytes .../Resources/icons/menu/groups_create@3x.png | Bin 0 -> 2010 bytes .../Resources/icons/menu/hide_members.png | Bin 0 -> 809 bytes .../Resources/icons/menu/hide_members@2x.png | Bin 0 -> 1675 bytes .../Resources/icons/menu/hide_members@3x.png | Bin 0 -> 2426 bytes .../icons/menu/info_notifications.png | Bin 0 -> 461 bytes .../icons/menu/info_notifications@2x.png | Bin 0 -> 853 bytes .../icons/menu/info_notifications@3x.png | Bin 0 -> 1302 bytes .../links_profile.png} | Bin .../links_profile@2x.png} | Bin .../links_profile@3x.png} | Bin Telegram/Resources/icons/menu/lock.png | Bin 0 -> 555 bytes Telegram/Resources/icons/menu/lock@2x.png | Bin 0 -> 932 bytes Telegram/Resources/icons/menu/lock@3x.png | Bin 0 -> 1322 bytes Telegram/Resources/icons/menu/network.png | Bin 0 -> 492 bytes Telegram/Resources/icons/menu/network@2x.png | Bin 0 -> 818 bytes Telegram/Resources/icons/menu/network@3x.png | Bin 0 -> 1249 bytes .../Resources/icons/menu/payment_email.png | Bin 0 -> 757 bytes .../Resources/icons/menu/payment_email@2x.png | Bin 0 -> 1461 bytes .../Resources/icons/menu/payment_email@3x.png | Bin 0 -> 2254 bytes Telegram/Resources/icons/menu/power_usage.png | Bin 0 -> 511 bytes .../Resources/icons/menu/power_usage@2x.png | Bin 0 -> 902 bytes .../Resources/icons/menu/power_usage@3x.png | Bin 0 -> 1227 bytes .../Resources/icons/menu/recovery_email.png | Bin 0 -> 508 bytes .../icons/menu/recovery_email@2x.png | Bin 0 -> 734 bytes .../icons/menu/recovery_email@3x.png | Bin 0 -> 1185 bytes Telegram/Resources/icons/menu/remove.png | Bin 0 -> 516 bytes Telegram/Resources/icons/menu/remove@2x.png | Bin 0 -> 823 bytes Telegram/Resources/icons/menu/remove@3x.png | Bin 0 -> 1271 bytes Telegram/Resources/icons/menu/signed.png | Bin 0 -> 578 bytes Telegram/Resources/icons/menu/signed@2x.png | Bin 0 -> 1069 bytes Telegram/Resources/icons/menu/signed@3x.png | Bin 0 -> 1528 bytes Telegram/Resources/icons/menu/stop_poll.png | Bin 519 -> 0 bytes .../Resources/icons/menu/stop_poll@2x.png | Bin 827 -> 0 bytes .../Resources/icons/menu/stop_poll@3x.png | Bin 1285 -> 0 bytes Telegram/Resources/icons/menu/storage.png | Bin 0 -> 670 bytes Telegram/Resources/icons/menu/storage@2x.png | Bin 0 -> 1192 bytes Telegram/Resources/icons/menu/storage@3x.png | Bin 0 -> 1816 bytes Telegram/Resources/icons/menu/timer.png | Bin 0 -> 608 bytes Telegram/Resources/icons/menu/timer@2x.png | Bin 0 -> 1250 bytes Telegram/Resources/icons/menu/timer@3x.png | Bin 0 -> 1883 bytes Telegram/Resources/icons/menu/topics.png | Bin 0 -> 345 bytes Telegram/Resources/icons/menu/topics@2x.png | Bin 0 -> 418 bytes Telegram/Resources/icons/menu/topics@3x.png | Bin 0 -> 775 bytes Telegram/Resources/langs/lang.strings | 11 ++++ .../SourceFiles/boxes/edit_privacy_box.cpp | 16 ++---- .../boxes/filters/edit_filter_box.cpp | 8 +-- .../boxes/peers/edit_linked_chat_box.cpp | 5 +- .../boxes/peers/edit_members_visible.cpp | 3 +- .../boxes/peers/edit_participants_box.cpp | 2 +- .../boxes/peers/edit_peer_info_box.cpp | 50 +++++++----------- .../boxes/peers/edit_peer_permissions_box.cpp | 7 +-- Telegram/SourceFiles/boxes/ringtones_box.cpp | 1 - .../admin_log/history_admin_log_inner.cpp | 2 +- .../view/history_view_context_menu.cpp | 2 +- Telegram/SourceFiles/info/info.style | 13 ++--- .../media/view/media_view_overlay_widget.cpp | 9 ---- .../menu/menu_antispam_validator.cpp | 2 +- .../settings_cloud_password_manage.cpp | 5 +- Telegram/SourceFiles/settings/settings.style | 32 +---------- .../settings/settings_advanced.cpp | 49 +++-------------- .../SourceFiles/settings/settings_advanced.h | 4 +- .../settings/settings_blocked_peers.cpp | 2 +- .../SourceFiles/settings/settings_chat.cpp | 35 ++++++++---- Telegram/SourceFiles/settings/settings_chat.h | 3 +- .../SourceFiles/settings/settings_common.cpp | 33 +----------- .../SourceFiles/settings/settings_common.h | 14 +---- .../settings/settings_experimental.cpp | 2 - .../SourceFiles/settings/settings_folders.cpp | 2 +- .../settings/settings_information.cpp | 9 ++-- .../SourceFiles/settings/settings_intro.cpp | 2 +- .../settings/settings_local_passcode.cpp | 5 +- .../SourceFiles/settings/settings_main.cpp | 30 ++++++----- .../settings/settings_notifications.cpp | 13 ++--- .../settings/settings_privacy_controllers.cpp | 14 ++--- .../settings/settings_privacy_controllers.h | 5 +- .../settings/settings_privacy_security.cpp | 31 +++++------ Telegram/SourceFiles/ui/menu_icons.style | 31 +++++++++-- 109 files changed, 184 insertions(+), 268 deletions(-) create mode 100644 Telegram/Resources/icons/menu/antispam.png create mode 100644 Telegram/Resources/icons/menu/antispam@2x.png create mode 100644 Telegram/Resources/icons/menu/antispam@3x.png create mode 100644 Telegram/Resources/icons/menu/bot_commands.png create mode 100644 Telegram/Resources/icons/menu/bot_commands@2x.png create mode 100644 Telegram/Resources/icons/menu/bot_commands@3x.png create mode 100644 Telegram/Resources/icons/menu/calls_receive.png create mode 100644 Telegram/Resources/icons/menu/calls_receive@2x.png create mode 100644 Telegram/Resources/icons/menu/calls_receive@3x.png create mode 100644 Telegram/Resources/icons/menu/chat_discuss.png create mode 100644 Telegram/Resources/icons/menu/chat_discuss@2x.png create mode 100644 Telegram/Resources/icons/menu/chat_discuss@3x.png create mode 100644 Telegram/Resources/icons/menu/devices.png create mode 100644 Telegram/Resources/icons/menu/devices@2x.png create mode 100644 Telegram/Resources/icons/menu/devices@3x.png create mode 100644 Telegram/Resources/icons/menu/dock_bounce.png create mode 100644 Telegram/Resources/icons/menu/dock_bounce@2x.png create mode 100644 Telegram/Resources/icons/menu/dock_bounce@3x.png create mode 100644 Telegram/Resources/icons/menu/experimental.png create mode 100644 Telegram/Resources/icons/menu/experimental@2x.png create mode 100644 Telegram/Resources/icons/menu/experimental@3x.png create mode 100644 Telegram/Resources/icons/menu/faq.png create mode 100644 Telegram/Resources/icons/menu/faq@2x.png create mode 100644 Telegram/Resources/icons/menu/faq@3x.png create mode 100644 Telegram/Resources/icons/menu/group_log.png create mode 100644 Telegram/Resources/icons/menu/group_log@2x.png create mode 100644 Telegram/Resources/icons/menu/group_log@3x.png create mode 100644 Telegram/Resources/icons/menu/group_reactions.png create mode 100644 Telegram/Resources/icons/menu/group_reactions@2x.png create mode 100644 Telegram/Resources/icons/menu/group_reactions@3x.png create mode 100644 Telegram/Resources/icons/menu/groups_create.png create mode 100644 Telegram/Resources/icons/menu/groups_create@2x.png create mode 100644 Telegram/Resources/icons/menu/groups_create@3x.png create mode 100644 Telegram/Resources/icons/menu/hide_members.png create mode 100644 Telegram/Resources/icons/menu/hide_members@2x.png create mode 100644 Telegram/Resources/icons/menu/hide_members@3x.png create mode 100644 Telegram/Resources/icons/menu/info_notifications.png create mode 100644 Telegram/Resources/icons/menu/info_notifications@2x.png create mode 100644 Telegram/Resources/icons/menu/info_notifications@3x.png rename Telegram/Resources/icons/{settings/premium/stories_links.png => menu/links_profile.png} (100%) rename Telegram/Resources/icons/{settings/premium/stories_links@2x.png => menu/links_profile@2x.png} (100%) rename Telegram/Resources/icons/{settings/premium/stories_links@3x.png => menu/links_profile@3x.png} (100%) create mode 100644 Telegram/Resources/icons/menu/lock.png create mode 100644 Telegram/Resources/icons/menu/lock@2x.png create mode 100644 Telegram/Resources/icons/menu/lock@3x.png create mode 100644 Telegram/Resources/icons/menu/network.png create mode 100644 Telegram/Resources/icons/menu/network@2x.png create mode 100644 Telegram/Resources/icons/menu/network@3x.png create mode 100644 Telegram/Resources/icons/menu/payment_email.png create mode 100644 Telegram/Resources/icons/menu/payment_email@2x.png create mode 100644 Telegram/Resources/icons/menu/payment_email@3x.png create mode 100644 Telegram/Resources/icons/menu/power_usage.png create mode 100644 Telegram/Resources/icons/menu/power_usage@2x.png create mode 100644 Telegram/Resources/icons/menu/power_usage@3x.png create mode 100644 Telegram/Resources/icons/menu/recovery_email.png create mode 100644 Telegram/Resources/icons/menu/recovery_email@2x.png create mode 100644 Telegram/Resources/icons/menu/recovery_email@3x.png create mode 100644 Telegram/Resources/icons/menu/remove.png create mode 100644 Telegram/Resources/icons/menu/remove@2x.png create mode 100644 Telegram/Resources/icons/menu/remove@3x.png create mode 100644 Telegram/Resources/icons/menu/signed.png create mode 100644 Telegram/Resources/icons/menu/signed@2x.png create mode 100644 Telegram/Resources/icons/menu/signed@3x.png delete mode 100644 Telegram/Resources/icons/menu/stop_poll.png delete mode 100644 Telegram/Resources/icons/menu/stop_poll@2x.png delete mode 100644 Telegram/Resources/icons/menu/stop_poll@3x.png create mode 100644 Telegram/Resources/icons/menu/storage.png create mode 100644 Telegram/Resources/icons/menu/storage@2x.png create mode 100644 Telegram/Resources/icons/menu/storage@3x.png create mode 100644 Telegram/Resources/icons/menu/timer.png create mode 100644 Telegram/Resources/icons/menu/timer@2x.png create mode 100644 Telegram/Resources/icons/menu/timer@3x.png create mode 100644 Telegram/Resources/icons/menu/topics.png create mode 100644 Telegram/Resources/icons/menu/topics@2x.png create mode 100644 Telegram/Resources/icons/menu/topics@3x.png diff --git a/Telegram/Resources/icons/menu/antispam.png b/Telegram/Resources/icons/menu/antispam.png new file mode 100644 index 0000000000000000000000000000000000000000..201dd2fbb917fffdf5c57b0b309621a6923c241e GIT binary patch literal 761 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfhpP3#WBP} z@N9^6c9x^Sw`1W=ZjLS;N!iJX0@;oRS{Z^YN1cLJb}aLlwS_B5UBgT%f<^bpA+HS{ zQ|lM_2Dk`L6^K{Z_qplL`y##SKDmwOH2IA0n{VE2{P}H>#s0UCO-lc>H*S4|PTFzB zV_93pxLU7fg|5H;w8+xBtvTx1=bc_ltqyEdnCNjO!=&k;LgI_ZE3dwaTi?Ejqx^`& z!@GImYLh2QR4KMP8P4?KD7>&^*RDlTY~MbA4ptDDU{IxKlDacSs416)sd4#o^eA4*oZ@GMDO|SrWF|b#Y+YlfJbfS{J$RoROXKbivd7{CuI#mZJs? z^Ur_(_b+ezZOI#boTiEg(l)I&yf^-Msbd*U$W6css*n zTa@k!MG>yVzJTWY@85s_z2jrRvyDDx?1K01-sNRue!*IA^7PxcX_Y&_1^s*Tvs!On nl#RoS?42=gfz@x`m;Gnb*8k~hI>S^NlP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG{YgYYR9Fe^SXn4_Z4~~@vrvYR zDMJ|wCFMr0L>VrSc`7#&7iN+&M7TgGG9(EX;!cL56f(P@kP8tRl6fA!=lj;*+dg|Z z=Wz7h{PxA^-D^E-t>>)0-u14%fBpKoelqZr0htWg+S=yl=TA>hZ*6V$_xFc|g=uMN z$sqpCjj5?=Mn=ZW%*^}y`-kjxetzE8))o*D@GqF?>FLG9#Eg!PK0Q4#8ZR#|V`F12 zEiHR{drXK0ZYwG(+}zy0pP7b+MsRR&Z*TAQ^))x-+}vDNR+hQBISYxzAU#MD7q+yt zl#`QVX=%w|Utjq9`!_c?pPZa<1E5?=OG_Oc9i{xDj8IHNLqiV_56tlO^%aRsNJua+ zFpx5oCwF#sURYRQ?yxvGIH<3$_xARdXC|vNHa1R6OPiXSdV70gu8`*3++10pqC0DA z>&weaa&d8S(b3Tn7#OIgrl!bFQg35plb4sbzP`?Afv6-}s3VWTv(sHh0e&(6+7E!0J&78Vweqn1UrDheMT zAK^hN@#2JNCrkYa6$zmdzDVfw^i;*zqULC-pC&|)OhrQ2R_LYNwn_XbL+a}4=(nPb z`9eTZbhuJeQ~5fj@3XVB$w@&$0nZ3KmsB{2cq-hA)DX@~!hxC4KpBaTk564)9guK^ zL4apMof@2|x_MghkT@@a)6~@Dp_REcG&CF>9MsX#0l2EF3I`d_3hf(`o0}VO;;C>e zP6+1?bjm(sS+A|Ft*)-Fkj>7{;MGhs4vJddypzUJ^k?TP!vX67-yxZR$Pcm!~;et z@YHCp2m@7hb+u5ny1EJ#E=(*uE%z2x1x$B$H&>`&-QL~~o8{$Y9u6CXiV@o4;$p70 zv9S?CTx)AV}2}c*8#U`b#Z({Qmwv zyc``JIXE~d5mXWq5fOm{8?JhKdL%*0D1w55=#>i0oSdALF%e?$Krxho4IUYt8$!${ zB&_Seozy8N$6aJ(Bz*`tK0f}o_xzHQ68t}0Xn^R1a>8>}y1KeeO-{|K-Q8VzdAV?aM+^)tNDO0- zDyE4{)$pSB=q1>_EEqm+bar;Ke{W`HR$E&Ou0$F$T2xe&sGn~ZqBe(zhuN8r@c6!g z4-y#bhzb~EQnRwM`sTp$-FbL;;G-g!7O|O_m{754$&af*Yj1ClNev$xH#ax&_)upG h41Ux<8Tj92;5YroZXIuc0jmH2002ovPDHLkV1hSGhSdN7 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/antispam@3x.png b/Telegram/Resources/icons/menu/antispam@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..43e7f8778c40fba0e72e5785bf6fe55358ebb123 GIT binary patch literal 2160 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 J);T3K0RYD4(+vOs literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/bot_commands@2x.png b/Telegram/Resources/icons/menu/bot_commands@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab6754f85ba47b1d4c1fde7f18a6ff076101cea GIT binary patch literal 821 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H6}vQ zf<@2j5)&fIzIGIRd=~djB(dyY{lCrWn~ZnAKHOYi!v=wQCmpQjzWZ8r`st_371v&W z{r$UoZP@N5EG&$(1iy501aZW5?Ruv!adG|W^S5u$UVSNVyS{skqJ+)z_;rh_e*LZ6 z8FTM#+1Sa{p+bK^`IFr}OKWfOP4?8^$4%YN9WB|`-_KwEI>SWj(wDgS__O&*8g(4+t~H8iY)U)9 zyG>?A^xA3Chfk+4XPV7cJ2fGkDRQ$QU(kXRvlOSvZrSNI-}L-ZsWX{TX1SZin@#&lm8VeD&DwX;(nGzrX*FwW36gA`6MbbAj}{@T;UF(iZa z?X10exsD>oCw4ozbg&0#=CkN4EL;>8)E%TP$iE=e>jPKF9;TzA9wLtzJAP4{-~6T*Q^TXrCd>YZ<}A_8w|*NN6SIbgQ!Q50?)rF}3nNcgl zAXV!IgO`Q_tLWrQOBGCE8RjH|} z`TP!*IWzJq51gKzeerZ*bGyOg3xEIG_D80zxdM#7jw`%UZ3fdV|E(}v>ivAiDF@@} z8Y!1g^;RA_G~wJ{51Xt-?76#su`*O$m&GnX7}ZU%&p@U#~QmA^7gHuPU1i)_!1}bG;1|6_p#lPMf!&vN_f0 z&SvRY&w?zTKL-yU+%-F47t^r=Q&%OdvMgQ9C;wwHQak_#hqCueyl4NBu-hGz^t*5K{Uk2YdYJp z?_z2(-=i#9_w3tu?Jmn@(GO2=9at@$wr~f+_o$`+<~Oh2^Oh~3T=-0{N%?li z!#8iveCM=GOfB}|Q>zE3PoMsC`l~_*^NvKXTW%%So(sft{9Y};;mzB(Pq$9LuWcOm z{{yqw9u>1=y^=YhnW`6C{QJE6b!Yv~|1oh!+w(2(oCnQ_3!IHVNH<-)`B^^C)DD#W NJzf1=);T3K0RX#q1#kcW literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/calls_receive.png b/Telegram/Resources/icons/menu/calls_receive.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe1f6699ca38b31c7a57865a6c0e13a2a0b339c GIT binary patch literal 773 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfhph9#WBP} z@NV$^?3alW^L^{K>^L}gg7mg0T`fYVRKqpiO^=G!Hr@G&y}&uDvrPGtN|4j)rRSz- zZ=I>KRxO)bcgd3z>p#!=Z2SD_hqGnnul9tW|6F4?|9S1a=e76t?*0EwARwKcon2g9 zylCefr-fT?zb&w+sju(9klXCGxYup*(WFMl-yMZ@b?*u+=FOjf`l-=Gk3GA08%yiTP^g#wIg!?^WQHs93P zq|EJ5uiA8bLXO$&#=UXM{l^clW%aC+_E9Ugn(MVR=#0IsZR?@{rHTb{>$e+DKPz(L ze2P)rdhz1fXP@1?DcK~|%Xa0*-}5_n?sODL6S)4@LxqX|dC|_c!-_e=GA*Z4j2ayp z?p(hfp7L1Y!ime5C*OX{b|%5VM@@LDSL@;5ULLEjHp$Jl3Y+D&_-9waRtt&WPoK7) ze{MbZT!zW4^UoI^d;K~)qU49(22-KVCr_TRh)G>zI`;MB$BnP~ggU>xy>4k^!!j>w zZC!P>wpjPg9I2;8=4$ zdS%GB-?f`>_RME;T{tJj=;qrpU9r=@<~y+%2z9z>h(z^2xV>Qe?YGSb7ldfNEZOz< z?^W{yEOztxcO4ebcaPK&`@%Z=@4tV1ud|G19!WC1vN}K`;riOJ*V|IUS*9%TTpILY zjX_$4qr{%$k8dvhDY7_dr9r9H+<%&%=L^*_L@0{&hCBMd2PHvIS3j3^P6Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NHBuPX;R9Fe^SXn4_UlhOQ%t_|q z$&}1z;?xI&1u$z1LcMogU3} z{=SHbqD1=o`U3+4A41pS;-ZI#$2S9edwXkZYs=xJzrSAy%zwPs*Vl-M2szv_s%mO# z-QC@AG&D5CutiBLE32267ua-kbYR!0uDQ8+W@hHa#RWuHS6A3dDw2_)XyBotp+QC~ zNmNu63~*UmTJmkYyu6;Co**qPE#*sVYHE&;kKNqd_#zg~@bIvPh6Z0pn3I&0^cT;E zrlzK#Mqgha6xP<(_);W3M8(C$9Fgp3R5_OHBT}1vr1bp!JZM)}S5;N6IpL71r20V9 z+1Z(znhMUy$Ov8-DM6(ig6k3D!^12Rsw*Km5+~)5Th$n*DN6;pf8|76aheQZ*S}C>yY{S`m#7E zeR+8a9XuKv8&f8S_!=5G5dZ}4%E}7v0RaJ=^dwQ8oSYC3^!fQY;c)IqZJNc%8(DUW z+2y!H!?CZ;z-ZV&LF}96LZv*9Q}sli+$A9)kPY?X-i8B z%Li>{W_Evn4-xt}M}$WL=!emeFk@q5@QWjY^NNj)jo{-rb98j%13Ef7f)p9{{QUfj zckS-(hJ}Uktz_aPCnu8^jg5_^21u#v>ud1a+S(|SCEDBDiIuc_MMVXx&!^S-EW*KQ zjGQ$!HOf9tKF9(^^_ul=)pkM@I+CfWSx{wk^SL zK|uk@Cv24|uF-)!Jv~`> zn89yvZ%I_tBPK~_XXnAeLBbKdqm`hyp^8~Qa?^NY^13K^cXxNz-pgrO@X+d{rUBo;xGEz`M zZYCN`b8|B`j`-c&+>DNnmfZ_l2{BAhPnX^5b4?!~AG%ZsFg-nu1R^7fxUs*#KLLcj z6W9ET6l47M_BIKCJ8H0}rw1h|?PDAV^PU`3gCl-1SM zQ&Ur93}?}xljY{-3U(l|e~cpWMK{^|G5Ic0mcW? UyZmchJpcdz07*qoM6N<$f literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/calls_receive@3x.png b/Telegram/Resources/icons/menu/calls_receive@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a74e596a73236e9a2be87b9a23377405465dd588 GIT binary patch literal 2124 zcmV-S2($NzP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?wn;=mRA>e5npbEQO%%tC#@<_0 zEQkf`gBp#!pkkt+v4I5@u{@}#FQOt6!SbNkzy>xf7^7Igf}+^4H|(Nf)Yv~`u=nzP zKbVihvSoJn-p%z2d!Npo`Olp5pK@l-nJrKt{DmVBjzBm9;Ru8y5RO3HjX;7gixLtN zDpaUYq(~8I_3`7!4i#EBCzDOxQhOP0KK>y`}(hoY}vzow<7MXS}J zefRF&KY#v=<~MKNRIFIh0rh`zqP}{Qyy23{{&p zZJauy>e{tyGK=TVopVA;xf%%hs~2Rhl!_B4PDHhz6B84E{raW)+P{ClUN_$I!Gj0F ztxK0K8hoEVeOOM9A3yf8CrzwQpFUl>bZJfJ?p2j5R~ADTELh;$pljEzf?u_2l?H$1 z$`yso%*@olsEU|XK~2BEfB%+R>=4;WIDY*2t5>g7ZR^&ponVxgEn8OAieL*i?kfOi z&YZzBc|10C#5jCKpO@KZAWoe+rJ6+Vk)vUS4LV}qzI}WB`n58&MvWRmQX}N#*>H!`%}B zs3ujaRFSr>La?k*=d#DRD2bum0A20lu2{HmVFi~XAUHRgC~}7m9R$N6QLv!SEt!b1lX0j2IQ*VJf4*hQ7Tl8P3QXwR zx33tzbm>ytehF^{lb)V#i;==($BqfuqcCE`2x%Uez-oN?oBA*k&=SpZ>{}kN?dXtswG=|&6+i{X3gR+`G&!R2d`hh-qf*uf+__^-Io!Kj<#>#E~bwhIntz&gsIfNefzd{?b;?l{Qe~mC%K)64<8y6-OA{V8#f9!+_!aB z3kG4sh7I|0C$cJzvV_Z<*iBluSg~T(4wo-qE*J#ToxWmfYlpEY;r@}G zoh^2oE+WbTT;h@?O9X>JMx;fH78?7Kl9I-Z8MAZe&I=bVT)K3L1USpMfB*i3c3kT; z4TG%WJ%NWMv3uIIX|}zO9zB}GtHAJ?xD)HLY|^C3x^?S#UK6BAppzRnZmXVe5x%EUHE6$CQ$)!bUSwJFmmG^uv)chnofD$_44dy?Ex<( ziZrXk+AzWr_S=dTD?}v%>6vcbx<%NXb2F}Wl8GSK;tUM5hHrQz)`lD256|i4rN);P zM~)m(BPfY#U(F)~ckbMooSAXz$OR1;Fd)M2?3zhXOY~${pKoPcjt(C_Y~+S~=+Ghk zqq}!=jF;?u_a7#+*m8J3@*gI8`}XZiPEIy~{r=syZJX+r)5V&k@Y`D?cpRfK3nV1* zndE=0mqw2|`0`10C9mWj8wLzZO-<#iKw%{UUnOwjahY^F^la@IH*TEtuzK}sTTDy~ zNqx)5Sz?(IU`VED(4axCFV`JAcKr0|lQeJJwrxx~I4R@W0X?44G}n0^D9HQHojaH4 zp+}D%r2Y7Shgi00)21s|t~54er#UJ5W94L2=gpgEIu&Fdya>cTvG#}FM+?u%CxI|D zeii726;B^z-MuUNlSp(TcV?pdiX|!wzltCr_4>k!8vL9N3T3_Kb0hw zW_D$UTIZ~d6KfJbDgP9_KlA;$J>Pe|`@{VCF6;BkdEafn?>le#+}7&XR{pokC!b6) zntnQUV}zf-|IM6l2?iCFl_#Hnp6Zpl{kH78MIJ|ho63GB-Smv%3^VV zM%CUpt*J={5*F10lD)@1R><_a@lVNL6rgeE?%j(SBG3QEefr9OZ|(K$wPD_m>=)k5 zVbed5%+=eqYwzB(Pm4Sb#?SLnTNaVbrh2Wox5TA7o`sifJOAX9Ez<1E#5YX{ c`#=AK{-bT2r(@?cZUZGYPgg&ebxsLQ0ErPs^#A|> literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/chat_discuss@2x.png b/Telegram/Resources/icons/menu/chat_discuss@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2b266a33d93b60ab8c92e2efbfd5223be713e137 GIT binary patch literal 1370 zcmV-g1*Q6lP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG#7RU!R9Fe^SXn4-T^v7#%tMlS zdLcsQBEt&_A?3{zDIsO16nPMege2lYgh!vLWJ-}lNfCw2Lp+dq=6=8Kx_8}e?|t?@ z>)i9z_i*;ZK5PHyVg3K>zm7toeY6qKM&Q4VfR5VY4Gj&WqM|%KJ?-u7?d}+N!US3|v zIt+poP1T5ng~jUXDrDZ@-#a=wve`5n;Y~!Hcm-u4-b!*mlsGL92}G+{Eoyn z1P`A4{QU124SbTW zK0Q4Zq5$dW=&-i7W{MC>ohJ$hFlH6VadC0sp_mQ&=jX$dqQ)m2hCxnFPCQ=zo&p-? z<85SQ#3R-0C*~@uiz`{gp!4%{PzY`3vi<#9*~Sn_8LAQb(AsfgyuQ8)bz=yW?oZ&t zpdw_X5RANJ7oMD)P@vL9KtKQ~f#l?5AyIjGIXUAbN(?T!ySoc{DI_y9ld!wFxly3h zVsCE`3_U$PLZZ>pQ3+EhDlsup$V(y9)6<~D;7cKNA<`IuYiw*REiJ{oj7tTt{%ve* zQd3i<%O3vV766r04R-;VxRxO4g@uK!t*u;?(zUCrE1A{%`uda-sf!@t!5jhK7-V3s zgNxXk81EU_>Szv}MK0ZF*k!pU0hli8f-1_>uzP?C-%gDvN zLilmpVjl1Wvi<%2$<%?j`W|LwWswMngoFrGFaXli(;prlAPR+|-qq!ovf|=mk~Qg0 zC}A3o&;I^?;?>>VT|z>F1hG77etsUZaGuJO3Mf$CU0q%1+Qgc*wKWXX0xCX0YG;8z zGBOezlarHNK9nnlNnc;z#l=P3=h1aAOCTdSy>TYs5X1l!7Z*pHH9AOpdwXSNrRYJ# zl`OMvX=#CT&}3<8Nl4KTi;9X+cf@JgH)^7(sYx^U7Be$5{G|ewot>SwwzkN9Z*MO) z0Wkvq{UB^O8bxiwKORUH8aggwGES?G#^sXg=4pi~-rn9s6uifj9TO9il9Gb_B28#u zs%v4Oq_-z(4gPGXudm0}VUWU3s&rGgwY8xcetdlF?Ck9B?&7_@y$$2oRcJZ15zt29 c{}q9M0UaQg)&P#xq5uE@07*qoM6N<$f=eZ7ng9R* literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/chat_discuss@3x.png b/Telegram/Resources/icons/menu/chat_discuss@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..2a54806d8542e833adf08207ceadb22383a04be5 GIT binary patch literal 2002 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 HN@xNA54B}A literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/dock_bounce.png b/Telegram/Resources/icons/menu/dock_bounce.png new file mode 100644 index 0000000000000000000000000000000000000000..b538f11e001f7434ceeb4c937804614d6602815b GIT binary patch literal 497 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfEZWn>F~maf zZScmfW(NV^N0SsXPK7kB&B(HMc*7#iyz-0rAGZC6y#n~wsMaqGD>KVnEYQ-j`}kYe z6x(9CnBob!b8dcr_V$d$=7>G#{ADBj1XVnRSQ;H~Z(I9%*3LO4-+$N6d;VUSU4Y~9 zgM1^cu17U?zxUPejG58!woIDkVBYr2CcTXj;-ZS8vDYIXJY;5K+;aTr_41=Xw&*{O zsgi0v_BOfoSHpd&dNr31n;u)VB?>rQGFiJSD@|`&>dkGnyYKof51ypLqLnCdtgvQp z-1XOAeMB#4M2fh!99BKSsx|Lt27j z^tZ14)Wd>;*y#%%KDfA_En?bJozoi_&riQ*;yWQwps0~cRC3?-z#t~FnK%@U>{#+3~r4oe$?zpDQFP+_w&L`#BaSB&1o zwU0km$ndovO)A`dx8>L}-Z_5h8zc6_t)H(ZzvF=5yJgO=EEw`kq-LfpGHT!L=-1Zq zSbX3+*M-^_XUefK&33lna??K_(EwCLvRuZxz4Xnn1+T{!&)!|S%RS2cF- zN`f1kZ!zf=w8StlPN}+ZDco$f>ets_cd0VCFTSY2;=n0*WHxJcSAapVlZ9{d*DBw? zJYJW3td6$a;4|NpXQFKA&bBl&ZOVkc&(>{6xb`Zq;#;P&METtRh4#xYu%EY(>J188 z>NltR&kOx+g-*sMDgWP=ownQ0Aa7A>4pwiC3e^&wV{Nv^z5i${RiZC|` zPB5P9H}h_^wEt@1h3b`vHftO-t=Uhvu4L07uwA~9w>!UK$E;Xq@oqG09$^AM#8@dQRO_DV77*HLfsAb$l&-eZj`cV9)Kh z6OMfQZF{jPZASiS@uLA6EzZg^e8N)IpWWMENqs5VVJBOC=6^eH^`bu$mXu8A@?eD| coK)6-j7sR literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/dock_bounce@3x.png b/Telegram/Resources/icons/menu/dock_bounce@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc6d3dd3c0a1845a3fccee06c08bad87fe0c12b GIT binary patch literal 1152 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbAj}{>szEF(iZa zZKSn-lB3A9Qd1GPo>o@Qj{YERoh^-CR(}|$%n&d&3^V)3AZ^WUI{Su{R<<&)2n%;^ z!zGD>hmIUtVR+GDx(o4C2VKmJ%@A;ZVQbgSX~`SV|^_6BI|*|R63St8rQ+IsfcH0j=B{?7Iei~<{u zIbC7!S{js}pWh}?dVoRffs~?T2GeobjTI9L4*M4OC9Vk365W5_pG&c_urP4-Rfh>i zbEWO(=NAnIi39lb)3WCkbJ2v;9^yr;BRkSEZZ+dTnmO_K7_{o$Y&C*GKo-LUaq%Oi0=(jRN z>GlG}!;e0%Xk6p$^N;KCawi!uHw51IHfjJl^B|%246^jN?qw?f>AAc`T`DWkw z;Y08rJ3G6$xHzQ)w{J&lWeG<|N8i4ETUt_bq0vnaw&tQwn{!$Od)?OFn?8NIxw-lF z?c4kN`dnVJ8*Z?&vDsqw!^!f6kfUQkw%}n7mw3VU<;$1Po-Lg!T~%N2FKTzuK_Wan zyog~(@Qiu$@txl+5fG0=swb zR=%@zsp=+PsU1RUCO?zfjdFQqlYZ9ByCxCiSY4RtC4RS{rCl{z>Kd2q5t+W6J@Iqr z%@exz@83ShyapzP#={d2KQ~hSaqWt|z5VexZZ7#$Vp3U%j^Ca)NGeOKYqh*cjnvi8zW@omRi;M$8R0Ez>_TW{PH^Wj45Td yE54Pbt@<*b_l$_+u|(Dg#;^v_1Ptmh*B_>c=Z~js4`#Oo<$h0BKbLh*2~7YVm(~3M literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/experimental.png b/Telegram/Resources/icons/menu/experimental.png new file mode 100644 index 0000000000000000000000000000000000000000..f84b745a9a6ecaabe9f29bbe58c792deb3d8458b GIT binary patch literal 582 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY=ftZV~B;| z+YsN=jDaF)Qi-Z!D%O#jre04LuMshuvT63QL-q@Ep84O1xZyobh@0EYD`G={-6^q& zd(O|^RXj`b@UORXuNKeye5d%lt$ec4-<#*x{N5C!C(hNnChYass#K=gXW5F)W|!`c zbu&6(?Bb|3RZFbfZhpSeOanWy?zLg7{gyA5?l^g6LDX8m<-w=&w%>MI=pfl;nHBJ- z#%?9&n$#5!auqg5#@gwA4pn&jH|p7HhmbG4b-%Z`C^gDm6gd6VXr@olGh;E=k3~CM z4kv2)F2B6}_Spr6A%D7A9WCacpX%lMbi?hpn{*C~_PT}Y$zFb1wD$Vz#~*KGZB?3h z!n^Tgj)~Or%bb#mm(Qj>F0d%!Gwyo4BW}I93}0Y(+3wo?_xanI`)uXg}k2i}L&Fpd7 z>wYsw?0m`YyFQDSJTI}D_%mr^f*rHL{PVhE++3$jPRS)O|N2{}ARxiRcFdV4)kegTe~DWM4f DccbK` literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/experimental@2x.png b/Telegram/Resources/icons/menu/experimental@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e56b0c3ed42d568bea8c7f69291c5d6668f291e5 GIT binary patch literal 991 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/faq@3x.png b/Telegram/Resources/icons/menu/faq@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..853065e0b7958f074889b24447fad036acd07ed8 GIT binary patch literal 1832 zcmV+@2iN$CP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>l1W5CRA>e5n%_%ROBBcZ;SY*5 zGz{}03yZWM>d7ck>Y<>ADDp+42qb(HUw!L8kO;(=q$CogBGC&%(St}o423c?C=~6N z{Q#q+l9YQtG?&F<&e{9S-e;eC>#)zmVeOgq%V(!X=zzlSg=O^)2C0DFJC@!;>6mu zYyF|X02|DRSV~Zfa)0rR^ycN|F|HPx$;nB8u3o)5s7XW+MBGw{2@D0*7M_3O#*M?n z!x{uz+_`h-Lb#+sAdV7LY0VhIoMy`uy=&L5nVA`FyS=?V<*ai@gDMni{g}h3yM{2F z44(FCadGkN*|T93rIJA{%Edj5X7If+-SrH7|Ni~{{rgiP4BwA(Oo(ro&eVGA>gp0R znB2N`Yk1Udl9s`z6|a~i8GTn87l!nlj5r1mg>}3Nw64e)8R@4_pML!K zk$6RZ*xlWI^ypEI-k-*tj43bxL?vkJM4O|M>BvuCC5SC#FRZNG>9NJRBuN9m~Tpx6mM_rlvTXX&Za@ z?!9y8j;MY0>Xpkrj%l59rF+KVm?Q{D=QKVadT?;iNHJ}2BastNv~P`#jfp&oEdUTO z083-O0j1=&sJs2UOgF;d;NeAxZ$B)Yza}W9`28?P~uSXAZcQ>RW{zkXdxZfy7D z{-l&%;F7j$FnaVYW>3c~zVsidBC`9hTJ^8Rk&zL+k{v&N{gwt##~cU7Qs?!Uo11&} z>ec-Ge8WJT<;=qV;l=OMuHTXdX^Gh7Tj}ObL}+TaLWd)oo0}^tDvT^!MSXoe=i+dx zG#Cxi(&qWb$oGVminP$-h%;x-7(x6wefo50uTY}FXpjMbMHQe)K}hpV@agI434kCO zs|4sbZ{8&9cW6r|rPAujgIEm4F(#qz3QD`DRGw22libP~!)(w;jvNUJLiowp5`XfFv*6i3FC(&V!h{)ij1URzu8p#YWrzzP3 zvghIt->6Gx=T$AfW#{F(nULA}IfU_z_onA03y&Z}01cOy7@hnhjuMGiW<6slgC9bO zZ6R21`Xd4Zf(taNYa!f21VO}Qb20?mmCMzcpo)wj2Ixz7gN70fsRjoy(glA@FyIf& zx+-jxr2moFN`Koadx_my_*tL0O+Kk}Wot&cKcXa>i*rF13BeiX_xW@--Km&3BAmPB zV`pN+rcIj?d1!g}?%l-1MCLwrj@~iL4GOpa%jfA9jQBAgfp`Ss5r{`19)Uk<1pWar Wq%JsXpUlSq0000 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/group_log.png b/Telegram/Resources/icons/menu/group_log.png new file mode 100644 index 0000000000000000000000000000000000000000..5452f7975ee42aa2a75f83859c91f1429db98fc5 GIT binary patch literal 539 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 b64toihV8A{9JBdFpc2H>)z4*}Q$iB}@KGIc literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/group_reactions.png b/Telegram/Resources/icons/menu/group_reactions.png new file mode 100644 index 0000000000000000000000000000000000000000..c941a59552777ddb5884c93bba069702c01b4c75 GIT binary patch literal 629 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf?7pXqV~B;| z+bO44uMCtpcGpuwa>tYj3psEV^sYW}OP;r5SBs7Q0^l#NC55+`aG#T1=1 z3@DnndG55m*MFJSAF#~+vhV!o>igHC?pN8%z5icT&Rio_ursD^|NQgM&1avDT3fdJ z?g5TgCm^?M_xtbFOM^1EM&&#V7UW==>GP~=Z`kUqK`TovWYi{i-pwnwkm30DyLMwl z3#-ChKjylypg z$Hjq41_v}2ezDKg5tHU?mpR5SlrTl$bEV(jxchI*IzO7dD6wK|ZcI4uw)!g9-j0uE z7cxv{pKW92W?{PhHcV+^K}FA!0F86cH(!69DkJBwHhJy!*98`59vTLjNX3tp)*{q)!0wNt%ZU#c9T9u;7myM^~+>RPX7hO&f!>rpooR z&!4j+;zT2nu&;8QT literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/group_reactions@2x.png b/Telegram/Resources/icons/menu/group_reactions@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..406d42273c8c0ad56c7363fea09341c41b8fe98b GIT binary patch literal 1164 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/groups_create.png b/Telegram/Resources/icons/menu/groups_create.png new file mode 100644 index 0000000000000000000000000000000000000000..5d6bb4c00e7cfa05599dfe555d6350845ff9a2b7 GIT binary patch literal 717 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfyvy{#WBP} z@NLNX?4N}a|9@0Yik~jJwrsHWPGA^}tU(LQ#-*%-W6G)Qmy{rmS{ zf2~pwNZ5SS{$+7_x%vF_wsQS$i+AqanYlG;X;A0Egg^26OM`aC=!tT%Mjm_oao62- zr=Q+Zh|#;AV`e?qZ)c3(vp0r$`T52(c~l>K{(0%8i9@H$rPp7r=JHw8)znP5E4U~) zi-qZ6j@jY{haWX|2Qo~!-un6bU(NdZ^=qkyh?S+~LY4a6Zi@>oWSah$Sn^bhl}WOZ%fEc= zL)G3mwaI~E{gGU)hb~_ZRuNjL@xeFOXy%I&E3NbO-!j>n59V!G{{QIFqOjEqV>jO{ z*?qUug-P7y+d<8{_wO$c(o_g|`{oVLvD2r$ZRFUkZ}5n9vu57AckkT!^PhkIdHh)U z*29I|er(kTv$x+4u$ACh7p5H};FoAMci+B!kN6HfELa+JFm>X56~4Y@SF;LR+}+*l zvp5dD|88$@pRdfaYgNc9*%$ihCQ`AnvAKtLl)e%FGE+rss@F%yOLw!s{I2!cE_-O5 sAy-vybNk!9|AnP`%XWM+XXdJ5T*Usr^VyrL=AfkH>FVdQ&MBb@0B&eC)Bpeg literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/groups_create@2x.png b/Telegram/Resources/icons/menu/groups_create@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cf110c03039fecd484c62fd8e93704bea285d1d1 GIT binary patch literal 1357 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/hide_members.png b/Telegram/Resources/icons/menu/hide_members.png new file mode 100644 index 0000000000000000000000000000000000000000..5e81f7655e53d26e435d0af9987d15e24c9c5aa8 GIT binary patch literal 809 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfoZa*i(`m| z;Mov+k5_>rVaDwLMNCXO7A$BI;ZsA>TvXEy@xEn|pEe8{L zCNJA^?%cVkwbO!DhO7+9&dx5|{rBNR#mOhnocW?|pvvERGG*SpdHeS7U%!66@R>h< zYWBvJ@4kCv?(EsdGkx@?XQ!s73avc;Sg`wOqr-x`dHH#HldddYymT*%qym|A}B1;LLMVF)2mQ_}EzT;q? z`sbm=I$(%(y68=R{k4j@OyWSow~rMv)tzpOFG!Uv30s|Nbo1LcGqr;1YU?xVrEUM@ z#kTOhW#IIck&~PBYi7K!w6&XUvX-3*KZF>&#W+*@9K|2>guEptcq6@T`N zpFbO4d}z`3FKS_c!gsd)JDhh|Nl4Dyu-|Uq`}dsp*fO}xYn&ctI#1BPr?WTCUN^x> kZvOf7jS<2^|1w{QpHvp?pa1)uKPZ`cy85}Sb4q9e0Cyo@o&W#< literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/hide_members@2x.png b/Telegram/Resources/icons/menu/hide_members@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e100565145ecaba06fa9bbafda99ab73dda55062 GIT binary patch literal 1675 zcmV;626Xv}P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NH^+`lQR9Fe^m}^XqZy3OJAeBhc zA`zi!EtO%ILo?}MG-I)vG;6CqSk_qPgSO0&!w0e1(AZkGmd)g})yjt~)1u>wBG|+>J=giUPWN>;X3Xd_8iC=BfV;c<@G2aVwxpyagTZj) z#tnOW`ynYBg0Qt#*4EaR*YNQ0{QP_e2L~-_WMFG+>x&mJ5+t6xckfP^Fkxi;&|0;! zvO0I}ocL5yQZjMkM4`@}Jv%);y{xS4&6_u$K7D%j?AetoSGH`~!Z&EeEdt|HW@e`N zbpQT+XJ=>3oH%hpFuLE0ii(8`7h1#*ozhdMPKj-z;^N{0_2|)~d3kyJ_wV1md-utc zCm%n4EDRq$d_YhYlSQ zpQ@^=+}zv-+E}$})%Wk;;kkM9=0NH&W5$gehsJ{k4~kEL z9H{s1-BTD-ff+MqeEj$k;U`a?%$hYzE4F?6b{vKD^5si^e}70TR;*AA&!0bsL=`KH z7LYPh1*awb872#;=Al6n6BA`JiP+YyTfct&3hDLh*MWh7T3yT=5z*B?JfWeXed-Jb zb}y}n88DGo#5{nV zWa-kSX=!PfE?vsa%{_hkbVNi1NVKe9zg~`5;nCsT9GeyMWJaj%;BTi&6 zEG+Er?DZ9a(T5>K@TX6oK6dO_qYPRkT}Ew@HR|lnojdd(wamsX`$Ak?oTH;73mp#? z-PYDtTwMI(#S5*5!C=%e(|#49$$uE0d;@wHEn0+s$l=rG&6~k69pwDdD!Y97^5n^r z2lGQ{Z0z{1Bq<9f#~&Z&HHR*niKnM0dIAChUcGvS5+-L;uBoEQrhEFdaq^Ev_m$$H z5apJFXL9)ClkPx`iHQ-519##+i=5a(Ub19~xgO~F3kwTT#g$y}US3{}jg6u_IXPJ| zt1Veu+W+6oRsGT$O6KnXiUvVBeF50Pnd1!&4ZvK8_wL;*YX4w}0mu!N$CODZ8h`gI zSGhfV_GlTHvGkP0Nfv5Khb4mJmhRSaS_C$FsQ@Y00o-D75W~hR+9FOFQ_9}07Nf(5 z4;y3o=QwIht$X}0<)7O(W{^|t!Gi}Rj$dQ+k-%}(aC!Pi|Lhux4;-~Q8iD=@`~>sU VZijJPoCyE`002ovPDHLkV1fsF8zBGy literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/hide_members@3x.png b/Telegram/Resources/icons/menu/hide_members@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..011d3c42590da4e88cf9800977aa182b2a45cf86 GIT binary patch literal 2426 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/info_notifications@2x.png b/Telegram/Resources/icons/menu/info_notifications@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ed724c7f240bc277888089bbdff3af686ebd5215 GIT binary patch literal 853 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H+y z<`8&V`1)GFycn^+(T9} zdBG2=_)4~_a{qQzNRb9Ki_^_>YmHxmtQ3BDT|eKIB+%fn?BumG$~R? zOu%aX`9=o^%fKEMS)~b03Moe!TxX_j=H%=tv0B@qzt*J_X!d9y? zR$B3DF5>+7{6yUP=_M6AWA@!kyf_P})5h`6Vl%$ep@t@gaqqwXe&6vYVciqm<%)zvHp<$|w7^ a%=L__{7Q0x9nqnnB<<UP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISe5S~*K4K@fgE@j^vG z!4nff@jwj3z{r0ff`J#}`7Z=9Gcgxb5CucgKn+yz0Kp^i48+J|1s4(T6L&wHrM7u9 zv(vAqW=7eb;89au^?g;<$D5h%P)J|80=fda0=feKK?P#`$+4oMqNb(>TKb zZvc-j8ZPUSk`h|kF-a6gpg9tN(0l_pD9kpVYo@2Csk=HmJGuA(MX;dYf|bR|&d#Q1 z3oO~Ov22fKfTI5ZP=H7_CKkJ%o*t@eYilbDCV>Euz(GWMKZM!YS%VvICF$wuyp#Z> zmmLuCf;*tmf7imof`h42^Wx&7Q8*AO0feNU4cF-%PrP@0|!K) zQ@~V7OkCOVpcA7~ghDAPDRg&tcSka#Ai|1t@)RmPCXXJVrA{os z;ww?>9#bk?fKIx{0xZ4~weB&cvIXd*drT;PObpxiV*!;}ROzvzq9Tgo`udt=MnQxv zE-q$7wuM4gx3{;(Tj0~vQ*v^$t*MI!zyjQ00i9S%7pV5Gc+0&_=deQT?(Qa11i-xaiK%>ect`}}!b!rUp`n35vBMw8Mgc}3s40y4`g)1LolLMPOo;4Xvz^HQ zwu|4rH#Ro@HYiKa&(B6;b8~abAS{ma^Yca|H#awqfX+{S$H&Kog@pjZ5rC7E6Guev z3BtJ5?P+|qQM!6z;nh7JE*IxtAQk7NxHJKB6GmZc_^aCacH5vy&XqR z^78WB4geSamzS4IOG{&8W7x%Y0i#`Y1$?c5)EtX8BAm1A@9&rN>g(&n#;l}q@Mc$6 zSC(s9gc4>R#j5hnLnaigU5sWjP(ImUw~mf$MD!Bf!a!|P&!-&hXfiW1CnqQAoD1ax z0ZKH%z|Qv@=<#|#G&Hofw)XS$f`D5eJ}Z2K(&!L%1#|^;1$?Z)Zw)omB}lGaTmS$7 M07*qoM6N<$f_}3{-v9sr literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/stories_links.png b/Telegram/Resources/icons/menu/links_profile.png similarity index 100% rename from Telegram/Resources/icons/settings/premium/stories_links.png rename to Telegram/Resources/icons/menu/links_profile.png diff --git a/Telegram/Resources/icons/settings/premium/stories_links@2x.png b/Telegram/Resources/icons/menu/links_profile@2x.png similarity index 100% rename from Telegram/Resources/icons/settings/premium/stories_links@2x.png rename to Telegram/Resources/icons/menu/links_profile@2x.png diff --git a/Telegram/Resources/icons/settings/premium/stories_links@3x.png b/Telegram/Resources/icons/menu/links_profile@3x.png similarity index 100% rename from Telegram/Resources/icons/settings/premium/stories_links@3x.png rename to Telegram/Resources/icons/menu/links_profile@3x.png diff --git a/Telegram/Resources/icons/menu/lock.png b/Telegram/Resources/icons/menu/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7c58bbbc1134acc9aabed7ed69349ecc66bc1a GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY^tY=V~B;| z*^rH0ha5z{Oloe(Y?`2?(D>SEmjes;#!VlX|1jAfFk8EIsr&^n*bdX4@g-n^1Y{j?VwIZ(Pp9en@ z_uiJ9Z#LV+cYi!TAu}4>}+I9QwA%mQ)QLXF@x*i^({+vH9W|ZuV;W@lODr&+6 zjguP=39#PHE8iOzzdqb+5i4isB%bc(TW`xIshl~&;Nh`mse_D*?EfQw>+Jg;>zw{% zb3D?ERY-_u!&I>c&rTH1IsDLI{`v6LS08`Wi0l@N&oz+nQ4>D@*>Cye3zuj3EuUev z@z~}K2YoEx$wp2qwVS^(rZus7=BgM0QHf*yO-Z?;D+*^U`}tX`VO2-ddd^R^|6hkP zd|KeYv**0|?6VId^`;l4`(F54$KT{27r5fiMFZ>lKRXk(d8NclY~-r<&RdrGN%sJQ z^qDKie7CK8y@PY(t+L&^x+mr}c8RRmVVS>O`teSM4QJDeZRB>Zi!OC@Nzm^;+O%xL cj&i2`L6V&;S4c literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/lock@2x.png b/Telegram/Resources/icons/menu/lock@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2887113a802a56e31424dff079e2114d3ea2ee0d GIT binary patch literal 932 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 GLK6U|Z!MGn literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/payment_email.png b/Telegram/Resources/icons/menu/payment_email.png new file mode 100644 index 0000000000000000000000000000000000000000..5a1c5886335ef2a20ce8b3f83c09cde985f0bcea GIT binary patch literal 757 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfhpe8#WBP} z@M?%N~Y3@{m6_;oxvFTD4WPte&rkM_QwlV5rNn_lzB|8xKTt)1xc<>;Q7 zKFhAZ))nhseDTHeM?T9h2dylrtJ}AI`}ggR{e69}zWyq(@KF&8TOGRi;)<(TlTVfu z7GBKT&feU3`|ZDCo#mG;XP=#Pa!G{Fy)9{*Z(htOv73MV@kigclN|Lrm@cbs^)-^< zSz#i>mmh2-@xps;n6qBU*~cH58V`Iw+Q{G$YBxXMM(*^}qEkAoCEp)DbX1xs;I;ay z+3d4^%ReU9!u3LL`}ET!pSyX}9rnicudS@AQkr1H=;-9C@QopG zNswmae&)oNVYOu^yp|S){{HoAml#Kd(aeCwzkgSEAKer=>ug%^%8(<%JNNA0pB#IQ ziE+y5r;{fOABu~OO?(}+QlytvajxIvYOdC%KUHPBWt*!jD;4!lryEGT`Tm=!u^}M( z`1;E)wWfM)jXIlRbmHjKqQL+8A&&|y)`q?Q^~*}ETTm#n!ba{=tVp-X{Q2|w`S_G3 za?ENd-MMq;!gtwC7c*Kk=Ul(Bo^k*E{)mXksK;3{Gkx0RbEn=^oqaYfMtpDF`%j;Q zHXk-Es;H=_s_MFzylH1l-t^DxvcE*RTJ!SrKc-LWe*5vG;r#Q~GJX8h<~QsRYIS<3 dF7r?Ha{O*tS>8ux!R(+U=jrO_vd$@?2>>11J?;Pi literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/payment_email@2x.png b/Telegram/Resources/icons/menu/payment_email@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a8af89a41e31ea3a387669df22bd6c9035dc012d GIT binary patch literal 1461 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/power_usage@2x.png b/Telegram/Resources/icons/menu/power_usage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2e46f6cb8cb6cebd7a1fd6e27ce54c8181a9f9 GIT binary patch literal 902 zcmV;119|+3P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NE@JU2LR9Fe^S5HVfQ4mhC%uGQ$ z6ck>>po5SSh*t{~0--KlL=Z2E1S$x1@+b&`hfvUShc4c_bPQ61u5~a91Oxw}5LiK0 zzmMO-W4E^F?kd<}_aL(~-^_f@yti-O%#8fV6_6_+S3s`7->!gyLeFS4uC1-j&dyRW zBTZjlU;Tc+Uaxyfzow)ZB9X}Z`};(Ad<$}U zdAYE#KuHbL_mnlA3-KtP1%w}^S5OBNQK%ShO93CFh zT{u>B3V*BBYOSoSa13L=6bc1I#xk&4twMt>EiEzYCK`-EFo5S)&Vjwnu? zRkpgiiV6dbPEmZMnVz4YYqc7Dv2!UAIE2U!7qxCep%9iGB?mFRyu3grS%{u3a0syt z0_Dxk&1^P{MhL`@gpEZ=tJR*JovGF8+uPfbnFIpHB9zPJ*4NjO>F(}MU_Lesr7f4s zdwY9qrF23*pU?02GqI!*(r7eJr?XTlu{X7f%}%&%u~_o?Ji%{lY+y^{_3`oX;o*Vk z1q%2ApPrs}IvwDpRA_E)4ohcoagpT9WHJ~3pF|=-l){Di`FV%K!QiD3N~hB}eShYw zR4O<@f9gePL=!R?48dR!4f^=_U}w6&zxR5*qCQ~6{q60Ie91%;!f>P0>F{ww<9fXw z6BH-0$K&aCyCjDw;rRHNSwQd-O^9SNnM|1A@H!X_c6WENoQX-GfO1x=)&2c_?0@1S zahJh=XJ-e$XxzcUfwZ%az1s;P=@r`E-bR#gIDCG7PQr~ZNGG(syqruXi^byjB-jx+ zEyLu>*zgTygLq|^%f+jwD4F?{WZ^|}D)jX9#GKn(TU)&7ag-R)n1ip;Xo!e2^%3^Z z5BVtObgx1D-Na>R;upnuJTBy-_`PLpg1CfW?5B^wY&@@wT{3~IG;e5GDOW(QfLsB& c0#m8LKO^&IsWn@r4gdfE07*qoM6N<$f-`cJNB{r; literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/power_usage@3x.png b/Telegram/Resources/icons/menu/power_usage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..cb81ed700dc2843e7897dcb96daf0bcaaeb01831 GIT binary patch literal 1227 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbHUhz+&p@;uw;_ z`Zmg2qcl*YY`u3}Wvfd^$^wHAUJ`*ylTtDi0+W`_PFW^l94yBy!YLdqtSa<0RLw>1 zV|&XU`M>|K*Z&ItT`u?C=G)Gq+vQu%-O66SbzlDazgEwG{p_5d;9#I2A;81I#=_jh zc(4J=tzl>8vvm{}7JmKem6w;7kdTm*lhc=yjY*$BeJUy}+_-IQs{Lyg74XVq-T-SwC5^V#Ss%CfAG~J$wEZl3wrbNWoK`% z>C4j<<_X#P`9Lcd$fTvE_3WPb^+1;6XD%m4$Hp^9q`P{0dT!s2J~VCeU<N%ltRObG-3IGTH*DLsZN-X?MN&cx+vwZ{93i>KJIlAuTO^=^Ep~ z=9ON>F)=dhjAZ(de0<;&~WuV21=nIoJM?RD{CK~I1G z@#DvX4c5qf|Ni~UiSQGrPtTq`o12Fxc)vB<>j00 zellz8=uDA+CGg|duV2^q%RgiLJ~^tm*qGbr`<7(um8S&-1tac@rWco#n0&Riv)i|O z_hNTFrgcYmI6qpZT>020>G0vhD_5?()hQ<<6A~W2fA8MR*^xORj!%oMxN}^9HC#Gh=lcm>3j~@f= zE1n7}FPl7h^2zme%Vl&6YZjW{o-$SL|EUAhrcYm4oSm(G@}9Vqv~+V*(~=!IVQR)7 zZ}tm4V3s#}x#aoZ^By-0bn4uK_OAQ=rrnBvSwY literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/recovery_email.png b/Telegram/Resources/icons/menu/recovery_email.png new file mode 100644 index 0000000000000000000000000000000000000000..a9d149fed2905b951987a59045dc16d7527210d8 GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfEX~u!F~maf zZLs3{rT~erwlg^YGP2F?6*T$4FVQN0;F-7CKgOP8ZW{$PM4F^IR~ZZ6;9+~NzhRck ztl9fEmv864(mgBg6Ilmn&?{d^g{JZ|(rUZ$!>YP4e!?@vK&oPvJ}y7sofV!_VTNJoK&LK$@)XSKrl zYaP$@KGNuFIRD&u`@9q4j_+MI==_rnod4ZXwq$SI`+MI%iv)`3Pv@?DwfJqB_3dpR z_ip^|%cpnX`hwTH-v5@_di!l!WSg4A+ow&szQI!hE}Xs3A2w-8+ e%FnB3-oGe*QN!`cIlvHSVDNPHb6Mw<&;$Upj=%f> literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/recovery_email@2x.png b/Telegram/Resources/icons/menu/recovery_email@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd378b84681a5cc27236c91a26e0e7629c20ab7c GIT binary patch literal 734 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H{xo!iDFu4CEL?OUIAH(!0FM`cj{RcO;$0Y?>G2|T(eldoM|{)L z%hJ-1>nT=lzIxaC`JC$KXWpb8D}0kIyMZr)eGT*cw+E*0+goB4t0QJSvqvt?qH1p( zfBWILWyVLiH`LhK&-GLM`1=Zb(}dO?0Xm;pTR#8%bH$Ivu%rF!k~kxuMR)TK-~L#! zCqgG_md;se=>=D_c-x&>Hs4%x`K5g0Vo`y~9Uh}ar zNBCBr|Gequ8vg)S1_rkZ0mpU@#?;!W4AZ%luVig~{L#Zy;K5X7&V_Q4%k(`y=u0s@ zvz_TP<+ECA!*qtzaamt}n*Fhubm8?^z3JRrTX|2HS-p)v#mq26|CH;*Uozk1W(hED zt$)|Q>GXnKk7vB#)^T=9C^dPlv10!^MXlz)-Tbq6IWD;Qa*giami>}?aT{*tXifE6 z8NyV%|Ni356;?e{w4}Z%)NLxKxxa<)z4*} HQ$iB}aF8%> literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/recovery_email@3x.png b/Telegram/Resources/icons/menu/recovery_email@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1372dd22cd3ba51100ed356d8912dae1f4cec9f8 GIT binary patch literal 1185 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbHUhz{2h6;uw;_ z`Zm(mTRTwT>W3=}1OlZG9&%C=t5e)2_OB^n`P5!B{v+qkSw%%~gf+EJRk(4Zv!mYO zkAhg!4bGK+j)+`*QT@EKbk5z~<=@lFH$ObMZf<_-hKL1l%D?l$rBZ^1NsxLUAy-6>(~DN ze*4nD4Gb9#J~n$9R|IHW*{bYbIAP<{#D$V)~@* zjtnLtu}W!+1TRP|RkhSzu_Eur%zc$jLn`vb-Lo)84>p;k6(@ry)w^dK{b#yfB|K;W7-Li?ebD=HSfpNltzz+6VKR!sLw%a7vOgMA?{QP_%a@Z`&o)hHa_M7^XxKbK|ML8Px%@9IOkcl$5AI27%YPIc z9PH+j%XrY^VJxG-hNuVj>{pu}N+!JC!kCqn6%`v>TVMbDNsd}qwx5EmLIcx+&iTCZ z!Rrp^^P5;&R#sP^uHL477R9 zA~f-x&bz`8QThIwqE?1a^EJK|H(3-Yv&hf;m)y?$X^zHzd;4$2kJ?!_ANrWca6c_W zfu~{WRMG1$55qO52|O@X-=}Huul?C$hl7qT3bl(?+{<~^GW*o?jkfbA#ILkj{p#G~ z$B!RAe7JV)TCb&x!V(_{`02d-Xjs*BztTmgtiHa#aMPYWHda<#D?WezoU~Cw@Uq(8 zXT6$28EP}@Ry@BI*A=2x=`7_P_c=rKX^-n={hv9O$tSbr)-IZ}G2@r&eA$9yr=D(l zbmiW?c{Y}Nl`bkIRrP#gF=<=>P>atx6m`?)>Aw zKE=hGIage(o5IFemG61Prk_^z`&R)N>~1<>$x8$KTH9o56Ma_r}FD&!(lP zr?6uJJ+pw(O$8=b^B_5 oe*TLKSsYO^BPeVBGiUwB)c^l$`h?v(K&1nNr>mdKI;Vst0FDg_T>t<8 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/remove.png b/Telegram/Resources/icons/menu/remove.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c9534cde412f43878f9ed9efef9c45c893b46b GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfEYH)$F~maf zZix1}W(SeCza?0t8W%{v;PZIJ5a=q){er3Bhnhu*p#{&nCKjPA4-a9P#w9unIKmp7 z_f9^3nj!VpGt(7)yXF_an`2#^F4nz0R{pz{FiYa@yX&rJ&wJju^0k%cq{km4LPe*3 zGVR@@lN~wj^)8v?OEaa87fwsfy}fGHuKVu=ZRVdZSbijs#p%?hGoNEbjs`xu?zlk% z7$~#TesgSLc=Frsut9A}jv^~V0zK{uwjWM(kew0GeQu>JZ$Q0T z0>fqLE2XmSR!{GAGw^VotP|okmvt2^oG-QfGN%aBZ#A)Jsyv6!-OVwZwQS;*3mmdKI;Vst015)UBLDyZ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/remove@2x.png b/Telegram/Resources/icons/menu/remove@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b064e3efeba522b84d08b3a70de077c6d26d4826 GIT binary patch literal 823 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H-C?r_u;xA`x>yf$ogfX1IarUi9( zZ&mG`=cD%f?>)(z=M1KLef|CSV@1u*n7aF!B?}v*PL;g~yVdEEWFYaTY_~%1#x-!RxH#Bo57rv^fe~kM2hpp!-9sFNZA;{z{j_=yYHK`c;G#SCEG6UF4`HhHth0KewE8F zOE%xs;A#lh5-nU(a#wZB-8|9I8*9Q=SJpIkFQCTuo z%x{hQ&Simb_WsS`(AC`kD80EL>Hb9SFZT^M-}I?F{^3*U{^S;6|01=@Ih$Sen1W=r z{dWa8oPGDH^z+C6|1VW@hkg-qRp@GnWckOCwBd~GsT(i)K#A7V)z4*}Q$iB}ths1s literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/remove@3x.png b/Telegram/Resources/icons/menu/remove@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..90b1b337d0deeb891fb5f85e6dae2c176477be9c GIT binary patch literal 1271 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISe5nmsRcK@`UqPoc01 zr9z0r%2HB_s6?aCDYo|ob`u{!!6)zq^a}CPP|$d(6oQ6Os3>fr!}EJGPfm_|_s-tE zckZse8HLQw%=w@HIrDO7&Y42N|9KC14|orF4|orF5Bxe0^km1Vudi=(bhK0|jg5^B z4h|NJMRe8c_4oJphlhu|ySvB7$Is8t>gCR;&B_``GYp{i6fj=!}SC5)R>d zdwVOD%GueO)t1Oc1Q7?sAteV;OG``F*VmD(V>bW-lmkMALfSPpGc&WbwY9LY5MYBX zmzS4EM@Kg|H~07VZ*Ol*Jo>QGhK7bFCML?|^5Wv+?Cfk$PmsgO$w{qNySlmxV7Bk) z`uh6U*Owj-HiN^%!?m@w;o;#pJm^MeL=ebDQ519Ve&+RMvAjdi5lFQsa1vqD)hKzvQP5C(K~xaGcfE_6p1$>;NF|Z$H$_8#T{mlU5(yL zpy5&x#~iVW?bzWS7>6b5^f$oOnXACTRG$N#e6Qlzqx+y#JPR2&XJroE0?Oe)>uYCO zpG%vZKp8HMs%hcU$wNTMG`*WPx!A3&tO%Li-CZen zq@W3CVoAsx92^)Y@eRrRz}Ac)1ms%^v1Z+)+lj_V+=2}^x?}rVWZ#Dv^el>PQ<3P> zz`%eJ<}PeOXyM9xH#8@`k~SkN>yy)Y%4VcI3cEyBX>uhBG^P!MrnVJ3EKWK4`HEKdzq z5prtSl%NtQ0H>y=8p>)KnqIiGZ7PO)cTtUP!~*VleSK|c7&FKDRBWh-NY$o>WS~HWjrc#BN2Vg9S~4l#PXn%`h@OBI$5@ z5X=MxO+(Xb4XUe;R836%wTEC5Yj*0ksfeTmPY66LFE87%vTlWdgj>kSq*B+t7tQ8o z=QA{b-MJ0sWgy hJ>WgyJrK_We*q~~w0$fjd}IIs002ovPDHLkV1h0RN8A7a literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/signed.png b/Telegram/Resources/icons/menu/signed.png new file mode 100644 index 0000000000000000000000000000000000000000..ab59a070d4213c59200094294108434c1403cf1d GIT binary patch literal 578 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY>lUjV~B;| z-QeA;4h0C9NYsdnbuC@WqVtC#Femql)0C2^s0?8rL4l2%xPLH4-E{dP{fFIEP$cIH zhnSe%qu+fjE<*aA51I=6ry5&Z-<)fkZS?oH^NG1T@8<2kyKevKvuVXUW2V$hKV51! zU*3)H{AaGI_G>*B{(bs-vuTsUr=NQ+zYMvq)U`-~BP*uYrd5W|ef{<3r>{OFZ9I`; zWRzz%JJ;-X-Tv^)JPIpAvi8Q^&oggJly_u7?HR@^2{PXD!93f|{g*sgpU%dQMr2ly1fv&LC ztyf(dw?^gejQRDqF85;H{`+ssHom)>CHlre!9aq?ZL#32xqizFg!P@HS$m3h_RKe* z?K?@u@Za6M>v`L4<<5VtYUOeb+!a!^Qzq_0h0U=CVXH-VHGD5hGm!XFwRcy@q?1n` y7A(k=Iz7>Exp3!^Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFmq|oHR9Fe^SX(G`Q51HWVG1FX z5{e=c<$+PkGDW_C#Kkn(^W`4Khedxtp31gIvD< zU$tjv{(m|%on!t#=RBO&{`Q*nt@G`@*4_jJ$d9amtiTUbAdvswsHmva)Knf3*FHNt zyS=>?8Z$mV{^8-_L(0eLbf%@Hc}^(E^Yr)6{=-Z5{bIpdGc(hNE`4!v;RS~PR|WIg-{042wO%Ro{{FtSw8SFtMtFF5 zY;5e^-JQeX;Bi0LiHV6=`j?j%=#9hCU@-V02}W5|R0LJNEPZWlEpzx*BR@YM3xsTN zs&{sFmY0{C&1MY$wye@77ZVe6etr&myWL(;P(Wn;{ry0WkB^0Oi+24O9v)6eNB}(+ z$Ye5627!UDwjw+y5j;yvOOXnmfq?-&+~VRQz}3}N9`tOx${v0ijmAUSQ;~0MY`914 z?(PP7e}CW6(ZObegM$kT3o+c;*K98m)xdOM?zIqRgA2xtPBXA_V#uLG9x`b zy|S_rmkWl7X+%dy6L(8X3&l-NPQJRj0(g3Qs#2+VGRTQ`MgC)BW0*pts;;iaCc_R! zRq@Wg+c!5i;Ghl!%H7>v3}0VgOG--kpx`?=IG}9s-rCxVh=^EOSz$;`O%3nwyFEWY zkD$;4tAJyR2$*0Lx2~>^{aO6>_6DEj<>j@tHF7>YJZxxa@I8YszpSXJptzfxn`B^J zm``YEDB1zx*yA96dV0c;Y7oZI&`?m2XeZb&UWGz&d3i~cc+bqtD3wY|C@U*#dwZLv zj*gDdB>fd+Ybc@omQho;?XwOTVWGTdkVfaK)l nw6?a=R`mnDOerhy-ze}2%QE>YpU;B300000NkvXXu0mjfQDf_l literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/signed@3x.png b/Telegram/Resources/icons/menu/signed@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..2a9e1011f005dd8c558503ad3338245871d77a4b GIT binary patch literal 1528 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=Vo5|nRA>e5T5Bj}Z4@4Sfyyc`u3rDRV1L+A4H zQbk3=;JU|KRi3V=r|00{fG2$N$JW+Xxc49$62kB@=h%ayA{vVk5F;Q)K#YJG0Wkvd ziGX}757`vvGVxsI)x8V~bD20M^XguT!dxbf@EdtC8B>ZmW^HY4X=!A7hv_qVaC39RIh}Qe z#kjYzv$K0f?)*#D#>QrMcb7c5zP@g1YKn}EEGjA@VOm<+Gjq()&~RyKi5ys4TXS)7 zAs;q3H#t>zcQ*;22WV_;B(GLhR!mJzS>7WfBb;`0bd;4VI}QyEC9e(-4;>sFc#O$0 zER{T!@;+*7Yj139kYkusLay7}+nhmXXQxoBq>_w`4DzbExmn24*w~m1ii?Yd1`m?_ zCA5C%z2g5V;SCH7Qd3j8zD`e1larH${rdX)l5l2bhOlgabhYAtmheeQNtBK3>}-i# z>+9>u#>~u2qK))I>8kttOL$zn|NQxrBmL{wFMWM|AwNw`P1>?2CntplkCn(HW8U80 zlnU&^5-G9){rdH*M4JZ*`EzV_b=B6^_Cf!TadYgRUW0>!Nn2A>Lzo3|adF(^?d@$< zRTZmJUOYQH%Vq4xj~^0Q`uqD+O557nB-$vQP(wolM=Xwab#=9nA7&tqVdNN`6xt}G z1OuB$r=+9^DdI>*2EDz#LIdd}{5d9FEBCww(xgQpPnZK(tMc>nISkADPnkGqfByU# zD=;Q1CN{4rML(LDm{3A;a&p-2ae^Kk93*Xdc{!_Pv5t;TMMVY9X`J!c*ckiIEzYdp zzkkO;VS0Mn-rk;lQH~f4j*pKyLcB_#rzeqzlatf@{5)B5;KPRxFh-Gt zB@_dJv~_iL5*v|=kZN|E#HfRWgal%9b#>+8;Xx`EV1GM1J0nX1?(XhL)Z*eINjQ*~ zm&d*?UtCa7K;3`*_z|6jhlgXoB4J`;qL2ZWQw%?{;Q>cS$AyIjR=Bmbm7AN3w}ef} zOA-(eKn%*t%7TJ|umF&xva(XxF|t8<<}6WX?dF4R`$+nUS6oVSoT;daZ$(F zI6FJDDq#2=7#PU5plGbBs)}8hrG?w?)ElQp5)zkxtb=VN}efvbfDR7)hesGeygu$3H&gA z;KISMp>WIh1>z2g66cB{O1i1nzdxP86*<4rcQbxHrZHx(TaLb z9)oI!+uOPqNj$&vnZb>#vrf2mZr|e>GyGW-mI%CQ*O)V1qswaYV~hLWf%aeYyHdJ0 zZhiiC=`){yuE~^Q{MFa-+V*03%VzD>Wn2zYkskMS`cRpUXO@geCwK5ylb# diff --git a/Telegram/Resources/icons/menu/stop_poll@2x.png b/Telegram/Resources/icons/menu/stop_poll@2x.png deleted file mode 100644 index 85fb5f82733e14f80261b462da96c7347132478f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 827 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H(?q< zsornDYZKC}6O<;tD6u;H@I#H=|1X(7i4Bs+%HD)s`&hB(_S?Mex2q-F4=0+<{=38U z--W&Nk4kVjXuG=|yP8$HGp1|v>#tUF{lEX#+073>$Ya#Xn3l9ZLZ@N%w1e)q-hO*o zB2;#5QP9d61&M7N7c@90HC5#qOYpRmY`<+f`|R_E^Tj!UrWKrh{yAu6 zivI^`Mu8n4_K2v9#f5S0DzTFF>0Qv!t;WIpPQ9<~O%hX_j#zx4a>OH*?BjC$MenNi z&SSAWu+<$VuJXSF|X)YtY*Y!f6W%-2LPkkoo^gAgT+B|&rNny&L7VhE@ zmwC-k(i#oTTBj}F+F`qW*_~jOm}GsiwhKxe;uc0tTcdPuR^6I?u;bA}!7YdSm9Ku7 zZRO&6@WIE5n6+WA-n>@6;KbK&@k!vAP3^8J=6`~2uL$m$a80V$&E(gf2}(R7jF(@A zJZ-d@>C>fre$CtkK5E7@eUc1$PaW32d;005vuWnDd(*`npB6=Gdu&c&GV5$S-dh=d zf^#9)MVr++Vh01(R$hx*o3=G2t73s@(c3BO6>@DJCi!Pcm(S)3KO+*MA?&=@J*8Uc1_;@LVta`6>RUCt;0k3afmwe2b=QtLn_UqxDJ9 zWp_Oj!e~5G2b`p?RI7zYUk$Oe?H^=_HXg_$2-*OPR@K% z_->kc?S`tj&VN%M{d{~%-L`+t25C2io&%dX{xBWgdm+)g^ZEl&!u53Zb6Mw<&;$V7 CyJi^x diff --git a/Telegram/Resources/icons/menu/stop_poll@3x.png b/Telegram/Resources/icons/menu/stop_poll@3x.png deleted file mode 100644 index 179ea1b890a980e92cecc1109a822d283e28029e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1285 zcmV+g1^W7lP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISe5nmsRcK@`V#H;-sg zP*4el2pyF~BO<6I3WX1_A3#B`^(}k@Lg6h!LsX)Xpdds^vR*1L&+|(rIXQOL>~-(V z+P%+J$n4y6{^x(ryxcQqZZ4PoWf90CkVPPiKo)^40(BRGd^kphLZQ36yT8A`x3{;g zt*y1S6ft<7jntbsZlcYZ6CCN28;oaj?GX00NXo#t`PdN`?7_nVXv%vCUdq zUS77>No?al0jRfD%mw$2iJ`~p_4RdbZZ5VD4n<%Am+)aU?3~o=>i+(oEzvg;_Y*S)}a0d0tkidi@0vlGtdqyhGe}#z<)G%cXwsDSll*) za6On4)6no~h%`r_*^WK#fl*juPQL?uVoq7-L#i=Sw_w9{%GT(i?E4Ud zp2Mo^88s%VOGcTqumh!LkAw!qpy#mba*WEEXmY}r_AfRx4NcFc^vCX!9_oS8rfwna ziVYStl?+ontSC^mlcES#SkewjO+(YORa}nAJVfo`;lWmxo~EXzot>Sru`%h1*El#h zAY*!ad;3mZ)6n#6dDm3b;eUB~v6ZD~YHBLESD*m`l)%~zO+(YOC0&l$(hbz(a?Df$ zjWh1>Y;SKfi;}wy2v80jO+(YORa|R~+XGRc%6WHpCqK&?tcvmR@fvI%&=Av_o^(pX z2-1LYX6=`YzVnfr=^nOOEp;M|V zt~PEG?3#)=y}!S2TxaxVW@f5{A@E}eNa8ERV%lungJyMy(^(q8@6;A^NZZp{eq+$m zSui!LFr6iCeB(Ai3Mr|&l)$%iUL%Z_G)(-b@+1;Xhz8zVV-ZQr-c^xBB_yC@(DIHF&>8`ACdyN}F7UWzypERQv<3AwYu9$^f!UnlA=K#()a#0xFF;q}Q9gu`H0;+R1t%px%14f8r ve5yWn4mcSy=qv(R1hNQZ5y&F&zeM046=cM4wIyJ&00000NkvXXu0mjfs~J5L diff --git a/Telegram/Resources/icons/menu/storage.png b/Telegram/Resources/icons/menu/storage.png new file mode 100644 index 0000000000000000000000000000000000000000..ce6ffd0a337441afb9ec4705bc829336c3476046 GIT binary patch literal 670 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfr-P@#WBP} z@NJ0q>dZumdwn8;oiXmGbQg&z+BY|Leq#T_u#`u_V96OC5#~byKbfC|>AB`P@4OOr zq9N$pX8yHXzel}(Q|p&rW|h66YXA4zdCz~}JNZ`Z%woCUOE8EQ~Z=#_k!qtELafy}t z;y@d@^8I;ioj4c{Fr*#Z5wq^})1>XUXC9uvm#>{UA%NpM z{~@Ow1BowHwue5qUVK~Deppe1XU2Y;i|R-1R|P6f{NeF$S$2k`6gyK?+p*eL2 zpT@1f{`)WAt@mdHXPFCXu`_cmx{h=z!QJFmgoN}ITyF?wR%JdShy%x9ks zTODd5Wh&LHBUTx~#Jl`vPT$cak4hW4Y!fLXmk0w3nZEn)`CFZ4EIa)0!u#*#yYJTR zclS`5xWLD3wy%arnbq77t=8Q|yYF&X?Ob~>;ecKLoTEt_Z@)d}I?-$C1cqNPZEFNwqtzxy q>P&O*J>Y&aP&I_5p6k>6_V(uSw2pUXO@geCwbQ48q+ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/storage@2x.png b/Telegram/Resources/icons/menu/storage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c38320ba7bfa2e189a213de8ecfbafa45371c8a1 GIT binary patch literal 1192 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 V`I`(6M6v(?002ovPDHLkV1l-wa@YU> literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/topics.png b/Telegram/Resources/icons/menu/topics.png new file mode 100644 index 0000000000000000000000000000000000000000..3e5eed67b49fad29561419763f848cc511c686f8 GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlfU9rko_46zVQ zPLNUc7yK_V{u3=|@v){`@Gsy)Cz(u*I>ku#j6^Z%_UI zeUl~$NlV|JsOR({{Cv!W4*#-!u>+8q%-kgct8L{)dZXM3QV^T zA7+-|R`;K`Wy=;7+9})hWp6GeVROiS&#KNt?%>B zd(W3*lj3M#;Al7?%Na2(ROF~dUy179)8Q2wa|31OJ@@TDwdqUk{N+ar-&pXK+Y6V* zF3eMNe~O?(MwW+g7deR&FR1U})U0u;8P3*k1mJ5?h|I_BnEM hYX~qf0>!I^Dj1{1+OAxFC(Ho~A5T|5mvv4FO#qe?jGF)e literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/topics@3x.png b/Telegram/Resources/icons/menu/topics@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c03b336571479a6b33cf086a0bc251f8276833 GIT binary patch literal 775 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbHUhz*Okz;uw;_ z`gYddbD54JZT9o$?_dbjG|_WV6cki+(snu+y5fq))^!d`1;2<`akU1Sc&wO^q4IC; zXEi^UG(V%?HOIxG~d z{Z%T-NiB8C*DBk&e)q5T9bbI^y*yw0_j%Qa^R`FpOxwM}{c(wvEML2N1Ro32{w4C) zd=38o{d+gVWZm`EOMXA$+tHxBLzwADy5aQGCl@}PDZ<0r>=^00Uk)fZ!S(2s>8F?4 zxV@WmHtqM{xNBDyv-dsl%Z&W!Rbn;wYt`EV3pw7aSzFx&-yTg8>~=;eS=@ zI?VT6%{q8j_{_1SjaRcu>(@>2o}fNIAv|69)tU~54M{WRU4ALU!?rih|M`TBc`P0c zVDuoFGviie%i)JPX4SVVL$s#8E&Kh?=8Ev?=bxVz-Awtl*mAC4V8$uwn)&CyZ}K%L zEBo(#?eWJ28GGM1vVaNWiQvy-F7F>{2XhG z-&_}qChMhzM~_T-oX_>QZvV}kq)wAp|08(y9(Z`SF4QpDefQnZ8Zj|nyU#mf^j3zP z@@Tho=zj9C!pnH3&jh*poO+|4wt(-Fox PphW2D>gTe~DWM4f!$Cb< literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 18396ecb4..2703e2c71 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -600,6 +600,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_username_label" = "Username"; "lng_settings_phone_label" = "Phone number"; "lng_settings_username_add" = "Add username"; +"lng_settings_username_about" = "Username lets people contact you on Telegram without needing your phone number."; +"lng_settings_add_account_about" = "You can add up to four accounts with different phone numbers."; "lng_settings_peer_to_peer_about" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio quality."; "lng_settings_advanced" = "Advanced"; "lng_settings_stickers_emoji" = "Stickers and emoji"; @@ -612,9 +614,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_theme_accent_title" = "Choose accent color"; "lng_settings_data_storage" = "Data and storage"; "lng_settings_information" = "Edit profile"; +"lng_settings_my_account" = "My Account"; "lng_settings_security" = "Security"; "lng_settings_passcode_title" = "Local passcode"; "lng_settings_sessions_title" = "Active sessions"; +"lng_settings_sessions_about" = "Review the list of devices where you are logged into your Telegram account."; "lng_settings_archive_title" = "Archive Settings"; "lng_settings_new_unknown" = "New chats from unknown users"; "lng_settings_auto_archive" = "Archive and Mute"; @@ -642,6 +646,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices."; "lng_settings_security_bots" = "Bots and websites"; "lng_settings_clear_payment_info" = "Clear Payment and Shipping Info"; +"lng_settings_logged_in" = "Logged In with Telegram"; +"lng_settings_logged_in_about" = "Websites where you've used Telegram to log in."; +"lng_settings_logged_in_title" = "Logged In with Telegram"; +"lng_settings_logged_in_description" = "You can log in on websites that support signing in with Telegram."; +"lng_settings_disconnect_all" = "Disconnect All Websites"; +"lng_settings_connected_title" = "Connected websites"; +"lng_settings_connected_about" = "Click to disconnect from your Telegram account."; "lng_settings_power_menu" = "Battery and Animations"; "lng_settings_power_title" = "Power Usage"; diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 51be4cd11..0baadc182 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -298,17 +298,11 @@ void EditPrivacyBox::setupContent() { CreateButton( content, rpl::duplicate(text), - st::settingsButton, - { - (always - ? &st::settingsIconPlus - : &st::settingsIconMinus), - always ? kIconGreen : kIconRed, - }))); + st::settingsButtonNoIcon))); CreateRightLabel( button->entity(), std::move(label), - st::settingsButton, + st::settingsButtonNoIcon, std::move(text)); button->toggleOn(rpl::duplicate( optionValue @@ -384,9 +378,9 @@ void EditPrivacyBox::setupContent() { }); addButton(tr::lng_cancel(), [this] { closeBox(); }); - const auto linkHeight = st::settingsButton.padding.top() - + st::settingsButton.height - + st::settingsButton.padding.bottom(); + const auto linkHeight = st::settingsButtonNoIcon.padding.top() + + st::settingsButtonNoIcon.height + + st::settingsButtonNoIcon.padding.bottom(); widthValue( ) | rpl::start_with_next([=](int width) { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 724dd7c47..88bbdca03 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -666,7 +666,7 @@ void EditFilterBox( content, tr::lng_filters_add_chats(), st::settingsButtonActive, - { &st::settingsIconAdd, 0, IconType::Round, &st::windowBgActive }); + { &st::settingsIconAdd, IconType::Round, &st::windowBgActive }); const auto include = SetupChatsPreview( content, @@ -693,7 +693,7 @@ void EditFilterBox( excludeInner, tr::lng_filters_remove_chats(), st::settingsButtonActive, - { &st::settingsIconRemove, 0, IconType::Round, &st::windowBgActive }); + { &st::settingsIconRemove, IconType::Round, &st::windowBgActive }); const auto exclude = SetupChatsPreview( excludeInner, @@ -746,13 +746,13 @@ void EditFilterBox( state->hasLinks.value() | rpl::map(!rpl::mappers::_1), tr::lng_filters_link_create(), st::settingsButtonActive, - { &st::settingsFolderShareIcon, 0, IconType::Simple }); + { &st::settingsFolderShareIcon, IconType::Simple }); const auto addLink = AddToggledButton( content, state->hasLinks.value(), tr::lng_group_invite_add(), st::settingsButtonActive, - { &st::settingsIconAdd, 0, IconType::Round, &st::windowBgActive }); + { &st::settingsIconAdd, IconType::Round, &st::windowBgActive }); SetupFilterLinks( content, diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp index 9e0bfa2d9..507023aa9 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "window/window_session_controller.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_boxes.h" #include "styles/style_info.h" #include "styles/style_settings.h" @@ -271,7 +272,7 @@ void Controller::choose(not_null chat) { above, tr::lng_manage_discussion_group_create(), st::infoCreateLinkedChatButton, - { &st::settingsIconChat, Settings::kIconLightBlue } + { &st::menuIconGroupCreate } )->addClickHandler([=, parent = above.data()] { const auto guarded = crl::guard(parent, callback); navigation->uiShow()->showBox(Box( @@ -291,7 +292,7 @@ void Controller::choose(not_null chat) { ? tr::lng_manage_discussion_group_unlink : tr::lng_manage_linked_channel_unlink)(), st::infoUnlinkChatButton, - { &st::settingsIconMinus, Settings::kIconRed } + { &st::menuIconRemove } )->addClickHandler([=] { callback(nullptr); }); Settings::AddSkip(below); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp index 5ef44326a..b51451817 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "lang/lang_keys.h" #include "styles/style_info.h" +#include "styles/style_menu_icons.h" namespace { @@ -53,7 +54,7 @@ namespace { rpl::single(QString()), [] {}, st::manageGroupTopicsButton, - { &st::infoRoundedIconHideMembers, Settings::kIconDarkBlue } + { &st::menuIconHideMembers } ))->toggleOn(rpl::single( (megagroup->flags() & ChannelDataFlag::ParticipantsHidden) != 0 ) | rpl::then(state->toggled.events())); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 4ec1fd6be..425e21c75 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -1684,7 +1684,7 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu( result->addAction( tr::lng_context_restrict_user(tr::now), crl::guard(this, [=] { showRestricted(user); }), - &st::menuIconRestrict); + &st::menuIconPermissions); } } if (user && _additional.canRemoveParticipant(participant)) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index febf08ed6..135fc46c9 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_icon.h" #include "api/api_invite_links.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_boxes.h" #include "styles/style_info.h" #include "styles/style_settings.h" @@ -307,11 +308,9 @@ private: void fillPendingRequestsButton(); void fillBotUsernamesButton(); -#if 0 // Enable after design improvements. void fillBotEditIntroButton(); void fillBotEditCommandsButton(); void fillBotEditSettingsButton(); -#endif void submitTitle(); void submitDescription(); @@ -348,9 +347,7 @@ private: void continueSave(); void cancelSave(); -#if 0 // Enable after design improvements. void toggleBotManager(const QString &command); -#endif void togglePreHistoryHidden( not_null channel, @@ -605,7 +602,7 @@ object_ptr Controller::createStickersEdit() { controller->show( Box(controller->uiShow(), channel)); }, - { &st::settingsIconStickers, Settings::kIconLightOrange }); + { &st::menuIconStickers }); Settings::AddSkip(container, bottomSkip); @@ -770,9 +767,6 @@ void Controller::fillPrivacyTypeButton() { && _peer->asChannel()->requestToJoin()), }; const auto isGroup = (_peer->isChat() || _peer->isMegagroup()); - const auto icon = isGroup - ? &st::settingsIconGroup - : &st::settingsIconChannel; AddButtonWithText( _controls.buttonsLayout, (hasLocation @@ -798,7 +792,7 @@ void Controller::fillPrivacyTypeButton() { : tr::lng_manage_private_peer_title)(); }) | rpl::flatten_latest(), [=] { showEditPeerTypeBox(); }, - { icon, Settings::kIconLightBlue }); + { &st::menuIconCustomize }); _privacyTypeUpdates.fire_copy(_typeDataSavedValue->privacy); } @@ -839,7 +833,7 @@ void Controller::fillLinkedChatButton() { std::move(text), std::move(label), [=] { showEditLinkedChatBox(); }, - { &st::settingsIconChat, Settings::kIconGreen }); + { isGroup ? &st::menuIconChannel : &st::menuIconGroups }); _linkedChatUpdates.fire_copy(*_linkedChatSavedValue); } // @@ -867,7 +861,7 @@ void Controller::fillForumButton() { rpl::single(QString()), [] {}, st::manageGroupTopicsButton, - { &st::settingsIconTopics, Settings::kIconPurple })); + { &st::menuIconTopics })); const auto unlocks = std::make_shared>(); button->toggleOn( rpl::single(_peer->isForum()) | rpl::then(unlocks->events()) @@ -924,7 +918,7 @@ void Controller::fillSignaturesButton() { tr::lng_edit_sign_messages(), rpl::single(QString()), [] {}, - { &st::infoRoundedIconSignature, Settings::kIconLightBlue } + { &st::menuIconSigned } )->toggleOn(rpl::single(channel->addsSignature()) )->toggledValue( ) | rpl::start_with_next([=](bool toggled) { @@ -986,7 +980,7 @@ void Controller::fillHistoryVisibilityButton() { : tr::lng_manage_history_visibility_hidden)(); }) | rpl::flatten_latest(), buttonCallback, - { &st::settingsIconChat, Settings::kIconGreen }); + { &st::menuIconChatBubble }); updateHistoryVisibility->fire_copy(*_historyVisibilitySavedValue); @@ -1001,11 +995,9 @@ void Controller::fillManageSection() { AddSkip(container, 0); fillBotUsernamesButton(); -#if 0 // Enable after design improvements. fillBotEditIntroButton(); fillBotEditCommandsButton(); fillBotEditSettingsButton(); -#endif Settings::AddSkip( container, st::editPeerTopButtonsLayoutSkipCustomBottom); @@ -1174,7 +1166,7 @@ void Controller::fillManageSection() { Data::PeerAllowedReactions(_peer), done)); }, - { &st::infoRoundedIconReactions, Settings::kIconRed }); + { &st::menuIconGroupReactions }); } if (canEditPermissions) { AddButtonWithCount( @@ -1193,7 +1185,7 @@ void Controller::fillManageSection() { }); }) | rpl::flatten_latest(), [=] { ShowEditPermissions(_navigation, _peer); }, - { &st::settingsIconKey, Settings::kIconGreen }); + { &st::menuIconPermissions }); } if (canEditInviteLinks) { auto count = Info::Profile::MigratedOrMeValue( @@ -1227,7 +1219,7 @@ void Controller::fillManageSection() { 0, 0)); }, - { &st::infoRoundedIconInviteLinks, Settings::kIconLightOrange }); + { &st::menuIconLinks }); wrap->toggle(true, anim::type::instant); } if (canViewAdmins) { @@ -1246,7 +1238,7 @@ void Controller::fillManageSection() { _peer, ParticipantsBoxController::Role::Admins); }, - { &st::infoRoundedIconAdministrators, Settings::kIconLightBlue }); + { &st::menuIconAdmin }); } if (canViewMembers) { AddButtonWithCount( @@ -1266,7 +1258,7 @@ void Controller::fillManageSection() { _peer, ParticipantsBoxController::Role::Members); }, - { &st::settingsIconGroup, Settings::kIconDarkBlue }); + { &st::menuIconGroups }); } fillPendingRequestsButton(); @@ -1283,7 +1275,7 @@ void Controller::fillManageSection() { _peer, ParticipantsBoxController::Role::Kicked); }, - { &st::settingsIconMinus, Settings::kIconRed }); + { &st::menuIconRemove }); } if (hasRecentActions) { auto callback = [=] { @@ -1295,7 +1287,7 @@ void Controller::fillManageSection() { tr::lng_manage_peer_recent_actions(), rpl::single(QString()), //Empty count. std::move(callback), - { &st::infoRoundedIconRecentActions, Settings::kIconPurple }); + { &st::menuIconGroupLog }); } if (canEditStickers || canDeleteChannel) { @@ -1337,7 +1329,7 @@ void Controller::fillPendingRequestsButton() { : tr::lng_manage_peer_requests_channel()), rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(), [=] { RequestsBoxController::Start(_navigation, _peer); }, - { &st::infoRoundedIconRequests, Settings::kIconRed }); + { &st::menuIconInvite }); std::move( pendingRequestsCount ) | rpl::start_with_next([=](int count) { @@ -1396,10 +1388,9 @@ void Controller::fillBotUsernamesButton() { [=] { _navigation->uiShow()->showBox(Box(UsernamesBox, user)); }, - { &st::infoRoundedIconInviteLinks, Settings::kIconLightOrange }); + { &st::menuIconLinks }); } -#if 0 // Enable after design improvements. void Controller::fillBotEditIntroButton() { Expects(_isBot); @@ -1409,7 +1400,7 @@ void Controller::fillBotEditIntroButton() { tr::lng_manage_peer_bot_edit_intro(), rpl::never(), [=] { toggleBotManager(u"%1-intro"_q.arg(user->username())); }, - { &st::settingsIconChat, Settings::kIconLightBlue }); + { &st::menuIconEdit }); } void Controller::fillBotEditCommandsButton() { @@ -1421,7 +1412,7 @@ void Controller::fillBotEditCommandsButton() { tr::lng_manage_peer_bot_edit_commands(), rpl::never(), [=] { toggleBotManager(u"%1-commands"_q.arg(user->username())); }, - { &st::settingsIconChat, Settings::kIconLightBlue }); + { &st::menuIconBotCommands }); } void Controller::fillBotEditSettingsButton() { @@ -1433,9 +1424,8 @@ void Controller::fillBotEditSettingsButton() { tr::lng_manage_peer_bot_edit_settings(), rpl::never(), [=] { toggleBotManager(user->username()); }, - { &st::settingsIconChat, Settings::kIconLightBlue }); + { &st::menuIconSettings }); } -#endif void Controller::submitTitle() { Expects(_controls.title != nullptr); @@ -1910,7 +1900,6 @@ void Controller::saveHistoryVisibility() { [=] { cancelSave(); }); } -#if 0 // Enable after design improvements. void Controller::toggleBotManager(const QString &command) { const auto controller = _navigation->parentController(); _api.request(MTPcontacts_ResolveUsername( @@ -1926,7 +1915,6 @@ void Controller::toggleBotManager(const QString &command) { } }).send(); } -#endif void Controller::togglePreHistoryHidden( not_null channel, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 420d5dc52..579049f28 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_info.h" +#include "styles/style_menu_icons.h" #include "styles/style_window.h" #include "styles/style_settings.h" @@ -818,7 +819,7 @@ void AddSuggestGigagroup( rpl::single(QString()), std::move(callback), st::manageGroupTopicsButton, - { &st::settingsIconAskQuestion, Settings::kIconGreen })); + { &st::menuIconChatDiscuss })); container->add( object_ptr( @@ -854,7 +855,7 @@ void AddBannedButtons( ParticipantsBoxController::Role::Restricted); }, st::manageGroupTopicsButton, - { &st::settingsIconKey, Settings::kIconLightOrange })); + { &st::menuIconPermissions })); if (channel) { container->add(EditPeerInfoBox::CreateButton( container, @@ -868,7 +869,7 @@ void AddBannedButtons( ParticipantsBoxController::Role::Kicked); }, st::manageGroupTopicsButton, - { &st::settingsIconMinus, Settings::kIconRed })); + { &st::menuIconRemove })); } } diff --git a/Telegram/SourceFiles/boxes/ringtones_box.cpp b/Telegram/SourceFiles/boxes/ringtones_box.cpp index ef2d096ec..eb06f7066 100644 --- a/Telegram/SourceFiles/boxes/ringtones_box.cpp +++ b/Telegram/SourceFiles/boxes/ringtones_box.cpp @@ -278,7 +278,6 @@ void RingtonesBox( st::ringtonesBoxButton, { &st::settingsIconAdd, - 0, Settings::IconType::Round, &st::windowBgActive }), diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index f6e5c92d5..e45f961c9 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1468,7 +1468,7 @@ void InnerWidget::suggestRestrictParticipant( editRestrictions(false, ChatRestrictionsInfo()); }).send(); } - }, &st::menuIconRestrict); + }, &st::menuIconPermissions); } void InnerWidget::restrictParticipant( diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index be51f4b50..409ae264d 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1199,7 +1199,7 @@ void AddPollActions( .confirmText = tr::lng_polls_stop_sure(), .cancelText = tr::lng_cancel(), })); - }, &st::menuIconStopPoll); + }, &st::menuIconRemove); } } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index c5567dd79..b1d81d809 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -410,15 +410,6 @@ infoIconMediaStories: icon {{ "info/info_media_stories", infoIconFg }}; infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }}; infoIconMediaStoriesRecent: icon {{ "info/info_stories_recent", infoIconFg }}; -infoRoundedIconRequests: icon {{ "info/edit/group_manage_join_requests", settingsIconFg }}; -infoRoundedIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg }}; -infoRoundedIconAdministrators: icon {{ "info/edit/group_manage_admins", settingsIconFg }}; -infoRoundedIconInviteLinks: icon {{ "info/edit/group_manage_links", settingsIconFg }}; -infoRoundedIconReactions: icon {{ "info/edit/group_manage_reactions", settingsIconFg }}; -infoRoundedIconSignature: icon {{ "info/edit/channel_manage_signature", settingsIconFg }}; -infoRoundedIconAntiSpam: icon {{ "info/edit/antispam", settingsIconFg }}; -infoRoundedIconHideMembers: icon {{ "info/edit/hidden_members", settingsIconFg }}; - infoIconShare: icon {{ "info/info_share", infoIconFg }}; infoIconEdit: icon {{ "info/info_edit", infoIconFg }}; infoIconDelete: icon {{ "info/info_delete", infoIconFg }}; @@ -473,8 +464,10 @@ infoBlockButton: SettingsButton(infoProfileButton) { textFg: attentionButtonFg; textFgOver: attentionButtonFgOver; } -infoCreateLinkedChatButton: SettingsButton(infoMainButton) { +infoCreateLinkedChatButton: SettingsButton(infoProfileButton) { padding: margins(74px, 10px, 8px, 8px); + textFg: lightButtonFg; + textFgOver: lightButtonFgOver; } infoUnlinkChatButton: SettingsButton(infoCreateLinkedChatButton) { textFg: attentionButtonFg; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 350590a0d..4078f03f6 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -549,15 +549,6 @@ OverlayWidget::OverlayWidget() handleMouseRelease(mousePosition(e), mouseButton(e)); } else if (type == QEvent::MouseMove) { handleMouseMove(mousePosition(e)); - } else if (type == QEvent::ContextMenu) { - const auto event = static_cast(e.get()); - const auto mouse = (event->reason() == QContextMenuEvent::Mouse); - const auto position = mouse - ? std::make_optional(event->pos()) - : std::nullopt; - if (handleContextMenu(position)) { - return base::EventFilterResult::Cancel; - } } else if (type == QEvent::MouseButtonDblClick) { if (handleDoubleClick(mousePosition(e), mouseButton(e))) { return base::EventFilterResult::Cancel; diff --git a/Telegram/SourceFiles/menu/menu_antispam_validator.cpp b/Telegram/SourceFiles/menu/menu_antispam_validator.cpp index b15ad5809..50dfea593 100644 --- a/Telegram/SourceFiles/menu/menu_antispam_validator.cpp +++ b/Telegram/SourceFiles/menu/menu_antispam_validator.cpp @@ -73,7 +73,7 @@ object_ptr AntiSpamValidator::createButton() const { rpl::single(QString()), [] {}, st::manageGroupTopicsButton, - { &st::infoRoundedIconAntiSpam, Settings::kIconPurple } + { &st::menuIconAntispam } ))->toggleOn(rpl::single( _channel->antiSpamMode() ) | rpl::then(state->toggled.events())); diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp index 7295604d2..2bc8e3964 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" /* @@ -133,7 +134,7 @@ void Manage::setupContent() { content, tr::lng_settings_cloud_password_manage_password_change(), st::settingsButton, - { &st::settingsIconKey, kIconLightBlue } + { &st::menuIconPermissions } )->setClickedCallback([=] { showOtherAndRememberPassword(CloudPasswordInputId()); }); @@ -143,7 +144,7 @@ void Manage::setupContent() { ? tr::lng_settings_cloud_password_manage_email_change() : tr::lng_settings_cloud_password_manage_email_new(), st::settingsButton, - { &st::settingsIconEmail, kIconLightOrange } + { &st::menuIconRecoveryEmail } )->setClickedCallback([=] { auto data = stepData(); data.setOnlyRecoveryEmail = true; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 8eaa3cd8f..4b95c2a5d 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -74,40 +74,10 @@ settingsUpdate: SettingsButton(infoMainButton, settingsButtonNoIcon) { settingsUpdateStatePosition: point(22px, 29px); settingsDividerLabelPadding: margins(22px, 8px, 22px, 16px); -settingsIconAccount: icon {{ "settings/account", settingsIconFg }}; -settingsIconNotifications: icon {{ "settings/notifications", settingsIconFg }}; settingsIconChat: icon {{ "settings/chat", settingsIconFg }}; -settingsIconFolders: icon {{ "settings/folders", settingsIconFg }}; -settingsIconGeneral: icon {{ "settings/advanced", settingsIconFg }}; -settingsIconLock: icon {{ "settings/lock", settingsIconFg }}; -settingsIconLanguage: icon {{ "settings/language", settingsIconFg }}; -settingsIconBattery: icon {{ "settings/battery", settingsIconFg }}; settingsIconInterfaceScale: icon {{ "settings/interface_scale", settingsIconFg }}; -settingsIconFaq: icon {{ "settings/faq", settingsIconFg }}; -settingsIconCalls: icon {{ "settings/calls", settingsIconFg }}; -settingsIconAskQuestion: icon {{ "settings/ask_question", settingsIconFg }}; -settingsIconTips: icon {{ "settings/tips", settingsIconFg }}; settingsIconStickers: icon {{ "settings/stickers", settingsIconFg }}; settingsIconEmoji: icon {{ "settings/emoji", settingsIconFg }}; -settingsIconThemes: icon {{ "settings/palette", settingsIconFg }}; -settingsIconGroup: icon {{ "settings/group", settingsIconFg }}; -settingsIconChannel: icon {{ "settings/channel", settingsIconFg }}; -settingsIconUser: icon {{ "settings/user", settingsIconFg }}; -settingsIconKey: icon {{ "settings/key", settingsIconFg }}; -settingsIconPlus: icon {{ "settings/plus", settingsIconFg }}; -settingsIconMinus: icon {{ "settings/minus", settingsIconFg }}; -settingsIconTimer: icon {{ "settings/timer", settingsIconFg }}; -settingsIconLaptop: icon {{ "settings/laptop", settingsIconFg }}; -settingsIconArrows: icon {{ "settings/arrows", settingsIconFg }}; -settingsIconEmail: icon {{ "settings/email", settingsIconFg }}; -settingsIconSound: icon {{ "settings/sound", settingsIconFg }}; -settingsIconDock: icon {{ "settings/dock", settingsIconFg }}; -settingsIconPin: icon {{ "settings/pin", settingsIconFg }}; -settingsIconDownload: icon {{ "settings/download", settingsIconFg }}; -settingsIconMention: icon {{ "settings/mention", settingsIconFg }}; -settingsIconTopics: icon {{ "settings/topics", settingsIconFg }}; -settingsIconTTL: icon {{ "settings/ttl", settingsIconFg }}; -settingsIconPhoto: icon {{ "settings/photo", settingsIconFg }}; settingsPremiumIconStories: icon {{ "settings/stories", settingsIconFg }}; settingsPremiumIconChannelsOff: icon {{ "settings/premium/channels_off", settingsIconFg }}; @@ -127,7 +97,7 @@ settingsStoriesIconViews: icon {{ "menu/show_in_chat", premiumButtonBg1 }}; settingsStoriesIconExpiration: icon {{ "settings/premium/timer", premiumButtonBg1 }}; settingsStoriesIconDownload: icon {{ "menu/download", premiumButtonBg1 }}; settingsStoriesIconCaption: icon {{ "settings/premium/stories_caption", premiumButtonBg1 }}; -settingsStoriesIconLinks: icon {{ "settings/premium/stories_links", premiumButtonBg1 }}; +settingsStoriesIconLinks: icon {{ "menu/links_profile", premiumButtonBg1 }}; settingsPremiumNewBadge: FlatLabel(defaultFlatLabel) { style: TextStyle(semiboldTextStyle) { diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 096bcbbdd..08d8355b2 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "settings/settings_common.h" #include "settings/settings_chat.h" -#include "settings/settings_experimental.h" #include "settings/settings_power_saving.h" #include "settings/settings_privacy_security.h" #include "ui/wrap/vertical_layout.h" @@ -46,8 +45,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "main/main_session.h" #include "mtproto/facade.h" -#include "styles/style_settings.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" #ifdef Q_OS_MAC #include "base/platform/mac/base_confirm_quit.h" @@ -96,7 +96,7 @@ void SetupConnectionType( tr::lng_connection_auto_connecting() | rpl::to_empty ) | rpl::map(connectionType), st::settingsButton, - { &st::settingsIconArrows, kIconGreen }); + { &st::menuIconNetwork }); button->addClickHandler([=] { controller->show(ProxiesBoxController::CreateOwningBox(account)); }); @@ -106,9 +106,7 @@ bool HasUpdate() { return !Core::UpdaterDisabled(); } -void SetupUpdate( - not_null container, - Fn showOther) { +void SetupUpdate(not_null container) { if (!HasUpdate()) { return; } @@ -140,24 +138,6 @@ void SetupUpdate( tr::lng_settings_install_beta(), st::settingsButtonNoIcon).get(); - if (showOther) { - const auto experimental = inner->add( - object_ptr>( - inner, - CreateButton( - inner, - tr::lng_settings_experimental(), - st::settingsButtonNoIcon))); - if (!install) { - experimental->toggle(true, anim::type::instant); - } else { - experimental->toggleOn(install->toggledValue()); - } - experimental->entity()->setClickedCallback([=] { - showOther(Experimental::Id()); - }); - } - const auto check = AddButton( inner, tr::lng_settings_check_now(), @@ -996,9 +976,7 @@ void Advanced::setupContent(not_null controller) { addDivider(); AddSkip(content); AddSubsectionTitle(content, tr::lng_settings_version_info()); - SetupUpdate(content, [=](Type type) { - _showOther.fire_copy(type); - }); + SetupUpdate(content); AddSkip(content); } }; @@ -1029,24 +1007,13 @@ void Advanced::setupContent(not_null controller) { if (cAutoUpdate()) { addUpdate(); } - if (!HasUpdate()) { - AddSkip(content); - AddDivider(content); - AddSkip(content); - content->add( - CreateButton( - content, - tr::lng_settings_experimental(), - st::settingsButtonNoIcon) - )->setClickedCallback([=] { - _showOther.fire_copy(Experimental::Id()); - }); - } AddSkip(content); AddDivider(content); AddSkip(content); - SetupExport(controller, content); + SetupExport(controller, content, [=](Type type) { + _showOther.fire_copy(type); + }); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_advanced.h b/Telegram/SourceFiles/settings/settings_advanced.h index 21aa2014f..a54e625f2 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.h +++ b/Telegram/SourceFiles/settings/settings_advanced.h @@ -30,9 +30,7 @@ void SetupConnectionType( not_null<::Main::Account*> account, not_null container); bool HasUpdate(); -void SetupUpdate( - not_null container, - Fn showOther); +void SetupUpdate(not_null container); void SetupWindowTitleContent( Window::SessionController *controller, not_null container); diff --git a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp index c9dce22cd..45f2376df 100644 --- a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp +++ b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp @@ -77,7 +77,7 @@ QPointer Blocked::createPinnedToTop(not_null parent) { content, tr::lng_blocked_list_add(), st::settingsButtonActive, - { &st::menuIconBlockSettings, 0, IconType::Round, &st::transparent } + { &st::menuIconBlockSettings, IconType::Round, &st::transparent } )->addClickHandler([=] { BlockedBoxController::BlockNewPeer(_controller); }); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 188d70140..6b353438b 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "settings/settings_advanced.h" +#include "settings/settings_experimental.h" #include "boxes/connection_box.h" #include "boxes/auto_download_box.h" #include "boxes/reactions_settings_box.h" @@ -67,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" // stickersRemove #include "styles/style_settings.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_window.h" namespace Settings { @@ -805,7 +807,7 @@ void SetupStickersEmoji( container, tr::lng_stickers_you_have(), st::settingsButton, - { &st::settingsIconStickers, kIconLightOrange } + { &st::menuIconStickers } )->addClickHandler([=] { controller->show(Box( controller->uiShow(), @@ -816,7 +818,7 @@ void SetupStickersEmoji( container, tr::lng_emoji_manage_sets(), st::settingsButton, - { &st::settingsIconEmoji, kIconDarkOrange } + { &st::menuIconEmoji } )->addClickHandler([=] { controller->show(Box(session)); }); @@ -1016,11 +1018,13 @@ void SetupMessages( void SetupExport( not_null controller, - not_null container) { + not_null container, + Fn showOther) { AddButton( container, tr::lng_settings_export_data(), - st::settingsButtonNoIcon + st::settingsButton, + { &st::menuIconExport } )->addClickHandler([=] { const auto session = &controller->session(); controller->window().hideSettingsAndLayer(); @@ -1029,6 +1033,15 @@ void SetupExport( session, [=] { Core::App().exportManager().start(session); }); }); + + AddButton( + container, + tr::lng_settings_experimental(), + st::settingsButton, + { &st::menuIconExperimental } + )->addClickHandler([=] { + showOther(Experimental::Id()); + }); } void SetupLocalStorage( @@ -1038,7 +1051,7 @@ void SetupLocalStorage( container, tr::lng_settings_manage_local_storage(), st::settingsButton, - { &st::settingsIconGeneral, kIconLightOrange } + { &st::menuIconStorage } )->addClickHandler([=] { LocalStorageBox::Show(&controller->session()); }); @@ -1069,7 +1082,7 @@ void SetupDataStorage( container, tr::lng_download_path(), st::settingsButton, - { &st::settingsIconFolders, kIconLightBlue }))); + { &st::menuIconShowInFolder }))); auto pathtext = Core::App().settings().downloadPathValue( ) | rpl::map([](const QString &text) { if (text.isEmpty()) { @@ -1097,7 +1110,7 @@ void SetupDataStorage( container, tr::lng_downloads_section(), st::settingsButton, - { &st::settingsIconDownload, kIconPurple } + { &st::menuIconDownload } )->setClickedCallback([=] { controller->showSection( Info::Downloads::Make(controller->session().user())); @@ -1155,15 +1168,15 @@ void SetupAutoDownload( add( tr::lng_media_auto_in_private(), Source::User, - { &st::settingsIconUser, kIconLightBlue }); + { &st::menuIconProfile }); add( tr::lng_media_auto_in_groups(), Source::Group, - { &st::settingsIconGroup, kIconGreen }); + { &st::menuIconGroups }); add( tr::lng_media_auto_in_channels(), Source::Channel, - { &st::settingsIconChannel, kIconLightOrange }); + { &st::menuIconChannel }); AddSkip(container, st::settingsCheckboxesSkip); } @@ -1507,7 +1520,7 @@ void SetupCloudThemes( edit, tr::lng_settings_bg_theme_edit(), st::settingsButton, - { &st::settingsIconThemes, kIconGreen } + { &st::menuIconPalette } )->addClickHandler([=] { StartEditor( &controller->window(), diff --git a/Telegram/SourceFiles/settings/settings_chat.h b/Telegram/SourceFiles/settings/settings_chat.h index e97653516..ad5b4f067 100644 --- a/Telegram/SourceFiles/settings/settings_chat.h +++ b/Telegram/SourceFiles/settings/settings_chat.h @@ -29,7 +29,8 @@ void SetupSupport( not_null container); void SetupExport( not_null controller, - not_null container); + not_null container, + Fn showOther); void PaintRoundColorButton( QPainter &p, diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index e48d6b742..27f125a09 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -47,38 +47,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Settings { -namespace { - -base::options::toggle OptionMonoSettingsIcons({ - .id = kOptionMonoSettingsIcons, - .name = "Mono settings and menu icons", - .description = "Use a single color for settings and main menu icons.", -}); - -} // namespace - -const char kOptionMonoSettingsIcons[] = "mono-settings-icons"; Icon::Icon(IconDescriptor descriptor) : _icon(descriptor.icon) { const auto background = [&]() -> const style::color* { if (descriptor.type == IconType::Simple) { return nullptr; - } else if (OptionMonoSettingsIcons.value()) { - return &st::transparent; - } else if (descriptor.color > 0) { - const auto list = std::array{ - &st::settingsIconBg1, - &st::settingsIconBg2, - &st::settingsIconBg3, - &st::settingsIconBg4, - &st::settingsIconBg5, - &st::settingsIconBg6, - (const style::color*)nullptr, - &st::settingsIconBg8, - &st::settingsIconBgArchive, - }; - Assert(descriptor.color < 10 && descriptor.color != 7); - return list[descriptor.color - 1]; } return descriptor.background; }(); @@ -111,11 +84,7 @@ void Icon::paint(QPainter &p, int x, int y) const { _backgroundBrush->first, _backgroundBrush->first); } - if (OptionMonoSettingsIcons.value()) { - _icon->paint(p, { x, y }, 2 * x + _icon->width(), st::menuIconFg->c); - } else { - _icon->paint(p, { x, y }, 2 * x + _icon->width()); - } + _icon->paint(p, { x, y }, 2 * x + _icon->width()); } int Icon::width() const { diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index 9ee703093..d601a1c1d 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -48,8 +48,6 @@ struct IconDescriptor; namespace Settings { -extern const char kOptionMonoSettingsIcons[]; - using Button = Ui::SettingsButton; class AbstractSection; @@ -129,15 +127,6 @@ public: } }; -inline constexpr auto kIconRed = 1; -inline constexpr auto kIconGreen = 2; -inline constexpr auto kIconLightOrange = 3; -inline constexpr auto kIconLightBlue = 4; -inline constexpr auto kIconDarkBlue = 5; -inline constexpr auto kIconPurple = 6; -inline constexpr auto kIconDarkOrange = 8; -inline constexpr auto kIconGray = 9; - enum class IconType { Rounded, Round, @@ -146,10 +135,9 @@ enum class IconType { struct IconDescriptor { const style::icon *icon = nullptr; - int color = 0; // settingsIconBg{color}, 9 for settingsIconBgArchive. IconType type = IconType::Rounded; const style::color *background = nullptr; - std::optional backgroundBrush; // Can be useful for gragdients. + std::optional backgroundBrush; // Can be useful for gradients. explicit operator bool() const { return (icon != nullptr); diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 9c9f3ccb6..74af35edf 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -26,7 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_controller.h" #include "window/notifications_manager.h" -#include "settings/settings_common.h" #include "storage/localimageloader.h" #include "data/data_document_resolver.h" #include "styles/style_settings.h" @@ -144,7 +143,6 @@ void SetupExperimental( addToggle(Ui::kOptionUseSmallMsgBubbleRadius); addToggle(Media::Player::kOptionDisableAutoplayNext); addToggle(kOptionSendLargePhotos); - addToggle(Settings::kOptionMonoSettingsIcons); addToggle(Webview::kOptionWebviewDebugEnabled); addToggle(kOptionAutoScrollInactiveChat); addToggle(Window::Notifications::kOptionGNotification); diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index 9b1087ac3..18a3630a7 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -543,7 +543,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { container, tr::lng_filters_create(), st::settingsButtonActive, - { &st::settingsIconAdd, 0, IconType::Round, &st::windowBgActive } + { &st::settingsIconAdd, IconType::Round, &st::windowBgActive } )->setClickedCallback([=] { if (showLimitReached()) { return; diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index fd04b4af8..da3f97859 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -370,7 +370,7 @@ void SetupRows( Info::Profile::NameValue(self) | Ui::Text::ToWithEntities(), tr::lng_profile_copy_fullname(tr::now), [=] { controller->show(Box(self)); }, - { &st::settingsIconUser, kIconLightBlue }); + { &st::menuIconProfile }); const auto showChangePhone = [=] { controller->show( @@ -383,7 +383,7 @@ void SetupRows( Info::Profile::PhoneValue(self), tr::lng_profile_copy_phone(tr::now), showChangePhone, - { &st::settingsIconCalls, kIconGreen }); + { &st::menuIconPhone }); auto username = Info::Profile::UsernameValue(self); auto empty = base::duplicate( @@ -426,9 +426,10 @@ void SetupRows( session->api().usernames().requestToCache(session->user()); }, box->lifetime()); }, - { &st::settingsIconMention, kIconLightOrange }); + { &st::menuIconUsername }); AddSkip(container); + AddDividerText(container, tr::lng_settings_username_about()); } void SetupBio( @@ -556,7 +557,6 @@ void SetupBio( void SetupAccountsWrap( not_null container, not_null controller) { - AddDivider(container); AddSkip(container); SetupAccounts(container, controller); @@ -786,7 +786,6 @@ not_null*> AccountsList::setupAdd() { st::mainMenuAddAccountButton, { &st::settingsIconAdd, - 0, IconType::Round, &st::windowBgActive })))->setDuration(0); diff --git a/Telegram/SourceFiles/settings/settings_intro.cpp b/Telegram/SourceFiles/settings/settings_intro.cpp index feea99acd..83c930c1a 100644 --- a/Telegram/SourceFiles/settings/settings_intro.cpp +++ b/Telegram/SourceFiles/settings/settings_intro.cpp @@ -69,7 +69,7 @@ object_ptr CreateIntroSettings( if (HasUpdate()) { AddDivider(result); AddSkip(result); - SetupUpdate(result, nullptr); + SetupUpdate(result); AddSkip(result); } { diff --git a/Telegram/SourceFiles/settings/settings_local_passcode.cpp b/Telegram/SourceFiles/settings/settings_local_passcode.cpp index 19b07ad24..e1c5d56d8 100644 --- a/Telegram/SourceFiles/settings/settings_local_passcode.cpp +++ b/Telegram/SourceFiles/settings/settings_local_passcode.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" namespace Settings { @@ -442,7 +443,7 @@ void LocalPasscodeManage::setupContent() { content, tr::lng_passcode_change(), st::settingsButton, - { &st::settingsIconLock, kIconLightBlue } + { &st::menuIconLock } )->addClickHandler([=] { _showOther.fire(LocalPasscodeChange::Id()); }); @@ -473,7 +474,7 @@ void LocalPasscodeManage::setupContent() { : tr::lng_passcode_autolock_inactive)(), std::move(autolockLabel), st::settingsButton, - { &st::settingsIconTimer, kIconGreen } + { &st::menuIconTimer } )->addClickHandler([=] { const auto box = _controller->show(Box()); box->boxClosing( diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 488676af2..9e6c8a2af 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" #include "styles/style_boxes.h" #include "styles/style_info.h" +#include "styles/style_menu_icons.h" #include #include @@ -263,7 +264,7 @@ void SetupPowerSavingButton( container, tr::lng_settings_power_menu(), st::settingsButton, - { &st::settingsIconBattery, kIconDarkOrange }); + { &st::menuIconPowerUsage }); button->setClickedCallback([=] { window->show(Box(PowerSavingBox)); }); @@ -282,7 +283,7 @@ void SetupLanguageButton( Lang::GetInstance().idChanges() ) | rpl::map([] { return Lang::GetInstance().nativeName(); }), icon ? st::settingsButton : st::settingsButtonNoIcon, - { icon ? &st::settingsIconLanguage : nullptr, kIconLightBlue }); + { icon ? &st::menuIconTranslate : nullptr }); const auto guard = Ui::CreateChild(button.get()); button->addClickHandler([=] { const auto m = button->clickModifiers(); @@ -324,22 +325,23 @@ void SetupSections( AddSkip(container); } else { addSection( - tr::lng_settings_information(), + tr::lng_settings_my_account(), Information::Id(), - { &st::settingsIconAccount, kIconLightOrange }); + { &st::menuIconProfile }); } + addSection( tr::lng_settings_section_notify(), Notifications::Id(), - { &st::settingsIconNotifications, kIconRed }); + { &st::menuIconNotifications }); addSection( tr::lng_settings_section_privacy(), PrivacySecurity::Id(), - { &st::settingsIconLock, kIconGreen }); + { &st::menuIconLock }); addSection( tr::lng_settings_section_chat_settings(), Chat::Id(), - { &st::settingsIconChat, kIconLightBlue }); + { &st::menuIconChatBubble }); const auto preload = [=] { controller->session().data().chatsFilters().requestSuggested(); @@ -352,7 +354,7 @@ void SetupSections( container, tr::lng_settings_section_filters(), st::settingsButton, - { &st::settingsIconFolders, kIconDarkBlue })) + { &st::menuIconShowInFolder })) )->setDuration(0); if (controller->session().data().chatsFilters().has() || controller->session().settings().dialogsFiltersEnabled()) { @@ -387,11 +389,11 @@ void SetupSections( addSection( tr::lng_settings_advanced(), Advanced::Id(), - { &st::settingsIconGeneral, kIconPurple }); + { &st::menuIconManage }); addSection( tr::lng_settings_section_call_settings(), Calls::Id(), - { &st::settingsIconCalls, kIconGreen }); + { &st::menuIconPhone }); SetupPowerSavingButton(&controller->window(), container); SetupLanguageButton(&controller->window(), container); @@ -440,7 +442,7 @@ void SetupInterfaceScale( container, tr::lng_settings_default_scale(), icon ? st::settingsButton : st::settingsButtonNoIcon, - { icon ? &st::settingsIconInterfaceScale : nullptr, kIconLightOrange } + { icon ? &st::menuIconShowInChat : nullptr } )->toggleOn(toggled->events_starting_with_copy(switched)); const auto ratio = style::DevicePixelRatio(); @@ -584,7 +586,7 @@ void SetupFaq(not_null container, bool icon) { container, tr::lng_settings_faq(), icon ? st::settingsButton : st::settingsButtonNoIcon, - { icon ? &st::settingsIconFaq : nullptr, kIconLightBlue } + { icon ? &st::menuIconFaq : nullptr } )->addClickHandler(OpenFaq); } @@ -600,7 +602,7 @@ void SetupHelp( container, tr::lng_settings_features(), st::settingsButton, - { &st::settingsIconTips, kIconLightOrange } + { &st::menuIconEmojiObjects } )->setClickedCallback([=] { UrlClickHandler::Open(tr::lng_telegram_features_url(tr::now)); }); @@ -609,7 +611,7 @@ void SetupHelp( container, tr::lng_settings_ask_question(), st::settingsButton, - { &st::settingsIconAskQuestion, kIconGreen }); + { &st::menuIconDiscussion }); const auto requestId = button->lifetime().make_state(); button->lifetime().add([=] { if (*requestId) { diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 6e0163629..87abcbc01 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_chat.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" @@ -826,7 +827,7 @@ void SetupNotificationsContent( ).make_state>(); const auto desktop = addCheckbox( tr::lng_settings_desktop_notify(), - { &st::settingsIconNotifications, kIconRed }, + { &st::menuIconNotifications }, desktopToggles->events_starting_with(settings.desktopNotify())); const auto flashbounceToggles = container->lifetime( @@ -837,7 +838,7 @@ void SetupNotificationsContent( : Platform::IsMac() ? tr::lng_settings_alert_mac : tr::lng_settings_alert_linux)(), - { &st::settingsIconDock, kIconDarkBlue }, + { &st::menuIconDockBounce }, flashbounceToggles->events_starting_with( settings.flashBounceNotify())); @@ -875,7 +876,7 @@ void SetupNotificationsContent( tr::lng_settings_sound_notify(), soundLabel->events_starting_with(label()), st::settingsButton, - { &st::settingsIconSound, kIconLightBlue }); + { &st::menuIconSoundOn }); AddSkip(container); @@ -902,7 +903,7 @@ void SetupNotificationsContent( ) | rpl::then(session->api().contactSignupSilent()); const auto joined = addCheckbox( tr::lng_settings_events_joined(), - { &st::settingsIconPlus, kIconGreen }, + { &st::menuIconInvite }, std::move(joinSilent) | rpl::map(!_1)); joined->toggledChanges( ) | rpl::filter([=](bool enabled) { @@ -914,7 +915,7 @@ void SetupNotificationsContent( const auto pinned = addCheckbox( tr::lng_settings_events_pinned(), - { &st::settingsIconPin, kIconLightOrange }, + { &st::menuIconPin }, rpl::single( settings.notifyAboutPinned() ) | rpl::then(settings.notifyAboutPinnedChanges())); @@ -935,7 +936,7 @@ void SetupNotificationsContent( const auto authorizations = &session->api().authorizations(); const auto acceptCalls = addCheckbox( tr::lng_settings_call_accept_calls(), - { &st::settingsIconCalls, kIconGreen }, + { &st::menuIconCallsReceive }, authorizations->callsDisabledHereValue() | rpl::map(!_1)); acceptCalls->toggledChanges( ) | rpl::filter([=](bool toggled) { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 349741dfb..f34691241 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "styles/style_settings.h" #include "styles/style_info.h" +#include "styles/style_menu_icons.h" #include #include @@ -762,7 +763,7 @@ object_ptr CallsPrivacyController::setupBelowWidget( controller, content, tr::lng_settings_calls_peer_to_peer_button(), - { &st::settingsIconArrows, kIconLightBlue }, + { &st::menuIconNetwork }, UserPrivacy::Key::CallsPeer2Peer, [] { return std::make_unique(); }, &st::settingsButton); @@ -1038,9 +1039,10 @@ object_ptr ProfilePhotoPrivacyController::setupAboveWidget( return nullptr; } -object_ptr ProfilePhotoPrivacyController::setupBelowWidget( +object_ptr ProfilePhotoPrivacyController::setupMiddleWidget( not_null controller, - not_null parent) { + not_null parent, + rpl::producer