From 17a10cf6bb759bf55c8827552beab10703c4d062 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 12:39:06 +0300 Subject: [PATCH 001/122] Added initial ability to handle history view swipe in both directions. --- .../history/history_view_swipe.cpp | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/history/history_view_swipe.cpp index cbe9d65a14..16190bc261 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe.cpp @@ -48,6 +48,8 @@ void SetupSwipeHandler( ChatPaintGestureHorizontalData data; SwipeHandlerFinishData finishByTopData; std::optional orientation; + std::optional direction; + int directionInt = 1.; QPointF startAt; QPointF delta; int cursorTop = 0; @@ -75,7 +77,8 @@ void SetupSwipeHandler( base::SafeRound(-overscrollRatio * threshold) )); state->data.msgBareId = state->finishByTopData.msgBareId; - state->data.translation = translation; + state->data.translation = translation + * state->directionInt; state->data.cursorTop = state->cursorTop; update(state->data); }; @@ -90,7 +93,9 @@ void SetupSwipeHandler( const auto processEnd = [=](std::optional delta = {}) { if (state->orientation == Qt::Horizontal) { const auto ratio = std::clamp( - delta.value_or(state->delta).x() / threshold, + delta.value_or(state->delta).x() + / threshold + * state->directionInt, 0., kMaxRatio); if ((ratio >= 1) && state->finishByTopData.callback) { @@ -108,6 +113,9 @@ void SetupSwipeHandler( setOrientation(std::nullopt); state->started = false; state->reached = false; + state->direction = std::nullopt; + state->startAt = {}; + state->delta = {}; }; scroll->scrolls() | rpl::start_with_next([=] { if (state->orientation != Qt::Vertical) { @@ -119,7 +127,19 @@ void SetupSwipeHandler( update(state->data); }; const auto updateWith = [=](UpdateArgs args) { - if (!state->started || state->touch != args.touch) { + if (!state->started + || state->touch != args.touch + || !state->direction) { + state->direction = (args.delta.x() == 0) + ? std::nullopt + : args.delta.x() < 0 + ? std::make_optional(Qt::RightToLeft) + : std::make_optional(Qt::LeftToRight); + state->directionInt = (!state->direction + || (*state->direction) == Qt::LeftToRight) + ? 1 + : -1; + state->started = true; state->touch = args.touch; state->startAt = args.position; @@ -146,7 +166,9 @@ void SetupSwipeHandler( } } else if (*state->orientation == Qt::Horizontal) { state->delta = args.delta; - const auto ratio = args.delta.x() / threshold; + const auto ratio = args.delta.x() + * state->directionInt + / threshold; updateRatio(ratio); constexpr auto kResetReachedOn = 0.95; constexpr auto kBounceDuration = crl::time(500); From 540fa0e66978d81539b82910b37c9e509d380479 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 15:13:56 +0300 Subject: [PATCH 002/122] Added ability to swipe-to-back to history widget. --- .../history/history_inner_widget.cpp | 37 ++++- .../history/history_inner_widget.h | 3 +- .../history/history_view_swipe.cpp | 133 +++++++++++++++++- .../SourceFiles/history/history_view_swipe.h | 9 +- .../history/history_view_swipe_data.h | 5 + .../SourceFiles/history/history_widget.cpp | 2 + .../view/history_view_replies_section.cpp | 4 +- Telegram/SourceFiles/ui/chat/chat.style | 2 + 8 files changed, 184 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e470c651ee..6dcf978acd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -440,7 +440,7 @@ HistoryInner::HistoryInner( }, _scroll->lifetime()); setupSharingDisallowed(); - setupSwipeReply(); + setupSwipeReplyAndBack(); } void HistoryInner::reactionChosen(const ChosenReaction &reaction) { @@ -523,12 +523,30 @@ void HistoryInner::setupSharingDisallowed() { }, lifetime()); } -void HistoryInner::setupSwipeReply() { - if (_peer && _peer->isChannel() && !_peer->isMegagroup()) { +void HistoryInner::setupSwipeReplyAndBack() { + if (!_peer) { return; } + const auto peer = _peer; HistoryView::SetupSwipeHandler(this, _scroll, [=, history = _history]( HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation > 0) { + if (!_swipeBackData.callback) { + _swipeBackData = HistoryView::SetupSwipeBack( + _widget, + [=]() -> std::pair { + auto context = preparePaintContext({}); + return { + context.st->msgServiceBg()->c, + context.st->msgServiceFg()->c, + }; + }); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } const auto changed = (_gestureHorizontal.msgBareId != data.msgBareId) || (_gestureHorizontal.translation != data.translation) || (_gestureHorizontal.reachRatio != data.reachRatio); @@ -541,9 +559,18 @@ void HistoryInner::setupSwipeReply() { repaintItem(item); } } - }, [=, show = _controller->uiShow()](int cursorTop) { + }, [=, show = _controller->uiShow()]( + int cursorTop, + Qt::LayoutDirection direction) { + if (direction == Qt::RightToLeft) { + return HistoryView::SwipeHandlerFinishData{ + .callback = [=] { _controller->showBackFromStack(); }, + .msgBareId = HistoryView::kMsgBareIdSwipeBack, + }; + } auto result = HistoryView::SwipeHandlerFinishData(); - if (inSelectionMode().inSelectionMode) { + if (inSelectionMode().inSelectionMode + || (peer->isChannel() && !peer->isMegagroup())) { return result; } enumerateItems([&]( diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 1288f6e7b4..855add11bc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -428,7 +428,7 @@ private: void reactionChosen(const ChosenReaction &reaction); void setupSharingDisallowed(); - void setupSwipeReply(); + void setupSwipeReplyAndBack(); [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; [[nodiscard]] bool hasCopyMediaRestriction( not_null item) const; @@ -544,6 +544,7 @@ private: base::Timer _touchScrollTimer; HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; + HistoryView::SwipeBackResult _swipeBackData; // _menu must be destroyed before _whoReactedMenuLifetime. rpl::lifetime _whoReactedMenuLifetime; diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/history/history_view_swipe.cpp index 16190bc261..7d6c54ca27 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe.cpp @@ -13,9 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "history/history_view_swipe_data.h" #include "ui/chat/chat_style.h" +#include "ui/painter.h" +#include "ui/rect.h" #include "ui/ui_utility.h" #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/scroll_area.h" +#include "styles/style_chat.h" #include @@ -30,7 +33,7 @@ void SetupSwipeHandler( not_null widget, not_null scroll, Fn update, - Fn generateFinishByTop, + Fn generateFinish, rpl::producer dontStart) { constexpr auto kThresholdWidth = 50; constexpr auto kMaxRatio = 1.5; @@ -145,8 +148,9 @@ void SetupSwipeHandler( state->startAt = args.position; state->delta = QPointF(); state->cursorTop = widget->mapFromGlobal(args.globalCursor).y(); - state->finishByTopData = generateFinishByTop( - state->cursorTop); + state->finishByTopData = generateFinish( + state->cursorTop, + state->direction.value_or(Qt::RightToLeft)); if (!state->finishByTopData.callback) { setOrientation(Qt::Vertical); } @@ -279,4 +283,127 @@ void SetupSwipeHandler( base::install_event_filter(widget, filter)); } +SwipeBackResult SetupSwipeBack( + not_null widget, + Fn()> colors) { + struct State { + base::unique_qptr back; + ChatPaintGestureHorizontalData data; + }; + + constexpr auto kMaxRightOffset = 0.5; + constexpr auto kMaxLeftOffset = 0.8; + constexpr auto kIdealSize = 100; + const auto maxOffset = st::swipeBackSize * kMaxRightOffset; + const auto sizeRatio = st::swipeBackSize + / style::ConvertFloatScale(kIdealSize); + + auto lifetime = rpl::lifetime(); + const auto state = lifetime.make_state(); + + const auto paintCallback = [=] { + const auto [bg, fg] = colors(); + const auto arrowPen = QPen( + fg, + st::lineWidth * 3 * sizeRatio, + Qt::SolidLine, + Qt::RoundCap); + return [=] { + auto p = QPainter(state->back); + + constexpr auto kBouncePart = 0.25; + constexpr auto kStrokeWidth = 2.; + constexpr auto kWaveWidth = 10.; + const auto ratio = std::min(state->data.ratio, 1.); + const auto reachRatio = state->data.reachRatio; + const auto rect = state->back->rect() + - Margins(state->back->width() / 4); + const auto center = rect::center(rect); + const auto strokeWidth = style::ConvertFloatScale(kStrokeWidth) + * sizeRatio; + + const auto reachScale = std::clamp( + (reachRatio > kBouncePart) + ? (kBouncePart * 2 - reachRatio) + : reachRatio, + 0., + 1.); + auto pen = QPen(bg); + pen.setWidthF(strokeWidth - (1. * (reachScale / kBouncePart))); + const auto arcRect = rect - Margins(strokeWidth); + auto hq = PainterHighQualityEnabler(p); + p.setOpacity(ratio); + if (reachScale) { + const auto scale = (1. + 1. * reachScale); + p.translate(center); + p.scale(scale, scale); + p.translate(-center); + } + { + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(rect); + p.drawEllipse(rect); + p.setPen(arrowPen); + p.setBrush(Qt::NoBrush); + const auto halfSize = rect.width() / 2; + const auto arrowSize = halfSize / 2; + const auto arrowHalf = arrowSize / 2; + const auto arrowX = st::swipeBackSize / 8 + + rect.x() + + halfSize + - arrowHalf; + const auto arrowY = rect.y() + halfSize; + + auto arrowPath = QPainterPath(); + arrowPath.moveTo(arrowX + arrowSize, arrowY); + arrowPath.lineTo(arrowX, arrowY); + arrowPath.lineTo(arrowX + arrowHalf, arrowY - arrowHalf); + arrowPath.moveTo(arrowX, arrowY); + arrowPath.lineTo(arrowX + arrowHalf, arrowY + arrowHalf); + + p.drawPath(arrowPath); + } + if (reachRatio) { + p.setPen(pen); + p.setBrush(Qt::NoBrush); + const auto w = style::ConvertFloatScale(kWaveWidth) + * sizeRatio; + p.setOpacity(ratio - reachRatio); + p.drawArc( + arcRect + Margins(reachRatio * reachRatio * w), + arc::kQuarterLength, + arc::kFullLength); + } + }; + }; + + const auto callback = ([=](ChatPaintGestureHorizontalData data) { + const auto ratio = std::min(1.0, data.ratio); + state->data = std::move(data); + if (ratio > 0) { + if (!state->back) { + state->back = base::make_unique_q(widget); + const auto raw = state->back.get(); + raw->paintRequest( + ) | rpl::start_with_next(paintCallback(), raw->lifetime()); + raw->setAttribute(Qt::WA_TransparentForMouseEvents); + raw->resize(Size(st::swipeBackSize)); + raw->show(); + raw->raise(); + } + state->back->moveToLeft( + anim::interpolate( + -st::swipeBackSize * kMaxLeftOffset, + maxOffset - st::swipeBackSize, + ratio), + (widget->height() - state->back->height()) / 2); + state->back->update(); + } else if (state->back) { + state->back = nullptr; + } + }); + return { std::move(lifetime), std::move(callback) }; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_view_swipe.h b/Telegram/SourceFiles/history/history_view_swipe.h index fe9ec2ea02..300e390402 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.h +++ b/Telegram/SourceFiles/history/history_view_swipe.h @@ -15,6 +15,9 @@ class ScrollArea; namespace HistoryView { struct ChatPaintGestureHorizontalData; +struct SwipeBackResult; + +constexpr auto kMsgBareIdSwipeBack = std::numeric_limits::max() - 77; struct SwipeHandlerFinishData { Fn callback; @@ -25,7 +28,11 @@ void SetupSwipeHandler( not_null widget, not_null scroll, Fn update, - Fn generateFinishByTop, + Fn generateFinishByTop, rpl::producer dontStart = nullptr); +SwipeBackResult SetupSwipeBack( + not_null widget, + Fn()> colors); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_view_swipe_data.h b/Telegram/SourceFiles/history/history_view_swipe_data.h index c3dd9eb776..3d2d356c4c 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_data.h +++ b/Telegram/SourceFiles/history/history_view_swipe_data.h @@ -17,4 +17,9 @@ struct ChatPaintGestureHorizontalData { int cursorTop = 0; }; +struct SwipeBackResult final { + rpl::lifetime lifetime; + Fn callback; +}; + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 48cecb1301..5c9f70dbf1 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -58,6 +58,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt_signal_producer.h" #include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" +#include "history/history_view_swipe.h" +#include "history/history_view_swipe_data.h" #include "base/call_delayed.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index dfd94af4fb..bc8219ff5b 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -904,7 +904,9 @@ void RepliesWidget::setupSwipeReply() { _history->owner().requestItemRepaint(item); } } - }, [=, show = controller()->uiShow()](int cursorTop) { + }, [=, show = controller()->uiShow()]( + int cursorTop, + Qt::LayoutDirection direction) { auto result = HistoryView::SwipeHandlerFinishData(); if (_inner->elementInSelectionMode(nullptr).inSelectionMode) { return result; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 1b0c786e90..3a44fffc2b 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1245,3 +1245,5 @@ newPeerUserpics: GroupCallUserpics { } newPeerUserpicsPadding: margins(0px, 3px, 0px, 0px); newPeerWidth: 320px; + +swipeBackSize: 150px; From d93d8ab1cc845e7bd168178959791f2c1bb69858 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 20:00:30 +0300 Subject: [PATCH 003/122] Added ability to provide ElasticScroll to SetupSwipeHandler. --- .../history/history_view_swipe.cpp | 28 ++++++++++++------- .../SourceFiles/history/history_view_swipe.h | 8 +++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/history/history_view_swipe.cpp index 7d6c54ca27..866f25d17d 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe.cpp @@ -31,7 +31,7 @@ constexpr auto kSwipeSlow = 0.2; void SetupSwipeHandler( not_null widget, - not_null scroll, + Scroll scroll, Fn update, Fn generateFinish, rpl::producer dontStart) { @@ -88,10 +88,15 @@ void SetupSwipeHandler( const auto setOrientation = [=](std::optional o) { state->orientation = o; const auto isHorizontal = (o == Qt::Horizontal); - scroll->viewport()->setAttribute( - Qt::WA_AcceptTouchEvents, - !isHorizontal); - scroll->disableScroll(isHorizontal); + v::match(scroll, [](v::null_t) { + }, [&](const auto &scroll) { + if (const auto viewport = scroll->viewport()) { + viewport->setAttribute( + Qt::WA_AcceptTouchEvents, + !isHorizontal); + } + scroll->disableScroll(isHorizontal); + }); }; const auto processEnd = [=](std::optional delta = {}) { if (state->orientation == Qt::Horizontal) { @@ -120,11 +125,14 @@ void SetupSwipeHandler( state->startAt = {}; state->delta = {}; }; - scroll->scrolls() | rpl::start_with_next([=] { - if (state->orientation != Qt::Vertical) { - processEnd(); - } - }, state->lifetime); + v::match(scroll, [](v::null_t) { + }, [&](const auto &scroll) { + scroll->scrolls() | rpl::start_with_next([=] { + if (state->orientation != Qt::Vertical) { + processEnd(); + } + }, state->lifetime); + }); const auto animationReachCallback = [=](float64 value) { state->data.reachRatio = value; update(state->data); diff --git a/Telegram/SourceFiles/history/history_view_swipe.h b/Telegram/SourceFiles/history/history_view_swipe.h index 300e390402..49d1468b10 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.h +++ b/Telegram/SourceFiles/history/history_view_swipe.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once namespace Ui { +class ElasticScroll; class RpWidget; class ScrollArea; } // namespace Ui @@ -24,9 +25,14 @@ struct SwipeHandlerFinishData { int64 msgBareId = 0; }; +using Scroll = std::variant< + v::null_t, + not_null, + not_null>; + void SetupSwipeHandler( not_null widget, - not_null scroll, + Scroll scroll, Fn update, Fn generateFinishByTop, rpl::producer dontStart = nullptr); From 05cd9eebb83ff3b7d4349986ee9cbb139e56459a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 20:18:57 +0300 Subject: [PATCH 004/122] Added ability to swipe-to-back to dialogs widget. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 54 +++++++++++++++++++ Telegram/SourceFiles/dialogs/dialogs_widget.h | 4 ++ 2 files changed, 58 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index b7f0605fbe..2a9d17f21e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_view_swipe.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_contact_status.h" #include "history/view/history_view_requests_bar.h" @@ -666,6 +667,7 @@ Widget::Widget( setupMoreChatsBar(); setupDownloadBar(); } + setupSwipeBack(); if (session().settings().dialogsFiltersEnabled() && (Core::App().settings().chatFiltersHorizontal() @@ -674,6 +676,58 @@ Widget::Widget( } } +void Widget::setupSwipeBack() { + HistoryView::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( + HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation > 0) { + if (!_swipeBackData.callback) { + _swipeBackData = HistoryView::SetupSwipeBack( + this, + []() -> std::pair { + return { + st::historyForwardChooseBg->c, + st::historyForwardChooseFg->c, + }; + }); + } + _swipeBackData.callback(data); + return; + } else { + if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } + } + }, [=](int, Qt::LayoutDirection direction) { + if ((direction != Qt::RightToLeft) + || _childListShown.current() + || (!controller()->isPrimary() && (_layout != Layout::Child)) + || (!controller()->shownForum().current() + && !controller()->openedFolder().current())) { + return HistoryView::SwipeHandlerFinishData(); + } + return HistoryView::SwipeHandlerFinishData{ + .callback = [=] { + _swipeBackData = {}; + if (const auto forum = controller()->shownForum().current()) { + const auto id = controller()->windowId(); + const auto initial = id.forum(); + if (!initial) { + controller()->closeForum(); + } else if (initial != forum) { + controller()->showForum(initial); + } + } else if (controller()->openedFolder().current()) { + if (!controller()->windowId().folder()) { + controller()->closeFolder(); + } + } + }, + .msgBareId = HistoryView::kMsgBareIdSwipeBack, + }; + }, nullptr); + +} + void Widget::chosenRow(const ChosenRow &row) { storiesToggleExplicitExpand(false); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 46da4b6954..a46907ef1d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "dialogs/dialogs_key.h" +#include "history/history_view_swipe_data.h" #include "window/section_widget.h" #include "ui/effects/animations.h" #include "ui/userpic_view.h" @@ -206,6 +207,7 @@ private: void setupDownloadBar(); void setupShortcuts(); void setupStories(); + void setupSwipeBack(); void storiesExplicitCollapse(); void collectStoriesUserpicsViews(Data::StorySourcesList list); void storiesToggleExplicitExpand(bool expand); @@ -383,6 +385,8 @@ private: ChatSearchTab _searchQueryTab = {}; ChatTypeFilter _searchQueryFilter = {}; + HistoryView::SwipeBackResult _swipeBackData; + SearchProcessState _searchProcess; SearchProcessState _migratedProcess; SearchProcessState _postsProcess; From 0d6a1e66104d03bc9f88366fe05340d7ef098aa7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 20:32:25 +0300 Subject: [PATCH 005/122] Added ability to swipe-to-back to replies section. --- .../view/history_view_replies_section.cpp | 29 +++++++++++++++++-- .../view/history_view_replies_section.h | 3 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index bc8219ff5b..11e8dfaa54 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -407,7 +407,7 @@ RepliesWidget::RepliesWidget( setupTopicViewer(); setupComposeControls(); - setupSwipeReply(); + setupSwipeReplyAndBack(); orderWidgets(); if (_pinnedBar) { @@ -877,7 +877,7 @@ void RepliesWidget::setupComposeControls() { } } -void RepliesWidget::setupSwipeReply() { +void RepliesWidget::setupSwipeReplyAndBack() { const auto can = [=](not_null still) { const auto canSendReply = _topic ? Data::CanSendAnything(_topic) @@ -892,6 +892,25 @@ void RepliesWidget::setupSwipeReply() { }; HistoryView::SetupSwipeHandler(_inner, _scroll.get(), [=]( HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation > 0) { + if (!_swipeBackData.callback) { + _swipeBackData = HistoryView::SetupSwipeBack( + this, + [=]() -> std::pair { + const auto context = listPreparePaintContext({ + .theme = listChatTheme(), + }); + return { + context.st->msgServiceBg()->c, + context.st->msgServiceFg()->c, + }; + }); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } const auto changed = (_gestureHorizontal.msgBareId != data.msgBareId) || (_gestureHorizontal.translation != data.translation) || (_gestureHorizontal.reachRatio != data.reachRatio); @@ -907,6 +926,12 @@ void RepliesWidget::setupSwipeReply() { }, [=, show = controller()->uiShow()]( int cursorTop, Qt::LayoutDirection direction) { + if (direction == Qt::RightToLeft) { + return HistoryView::SwipeHandlerFinishData{ + .callback = [=] { controller()->showBackFromStack(); }, + .msgBareId = HistoryView::kMsgBareIdSwipeBack, + }; + } auto result = HistoryView::SwipeHandlerFinishData(); if (_inner->elementInSelectionMode(nullptr).inSelectionMode) { return result; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 5d7758b7fb..faab901de2 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -234,7 +234,7 @@ private: void finishSending(); void setupComposeControls(); - void setupSwipeReply(); + void setupSwipeReplyAndBack(); void setupRoot(); void setupRootView(); @@ -394,6 +394,7 @@ private: rpl::lifetime _topicLifetime; HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; + HistoryView::SwipeBackResult _swipeBackData; SendPaymentHelper _sendPayment; From 13a93102a555867a1cf21436bfc7b1cba20098d4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 21:30:56 +0300 Subject: [PATCH 006/122] Added ability to swipe-to-back to admin log section. --- .../admin_log/history_admin_log_inner.cpp | 18 ++++++---- .../admin_log/history_admin_log_inner.h | 3 ++ .../admin_log/history_admin_log_section.cpp | 34 +++++++++++++++++++ .../admin_log/history_admin_log_section.h | 4 +++ 4 files changed, 52 insertions(+), 7 deletions(-) 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 a7ea96d4c0..61a599cac4 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1039,6 +1039,16 @@ void InnerWidget::restoreScrollPosition() { _scrollToSignal.fire_copy(newVisibleTop); } +Ui::ChatPaintContext InnerWidget::preparePaintContext(QRect clip) const { + return _controller->preparePaintContext({ + .theme = _theme.get(), + .clip = clip, + .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)), + .visibleAreaTop = _visibleTop, + .visibleAreaWidth = width(), + }); +} + void InnerWidget::paintEvent(QPaintEvent *e) { if (_controller->contentOverlapped(this, e)) { return; @@ -1051,13 +1061,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); - auto context = _controller->preparePaintContext({ - .theme = _theme.get(), - .clip = clip, - .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)), - .visibleAreaTop = _visibleTop, - .visibleAreaWidth = width(), - }); + auto context = preparePaintContext(clip); if (_items.empty() && _upLoaded && _downLoaded) { paintEmpty(p, context.st); } else { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index 209cc0384e..ed23bdad44 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -36,6 +36,7 @@ class PopupMenu; class ChatStyle; class ChatTheme; struct PeerUserpicView; +struct ChatPaintContext; } // namespace Ui namespace Window { @@ -69,6 +70,8 @@ public: return _channel; } + Ui::ChatPaintContext preparePaintContext(QRect clip) const; + // Set the correct scroll position after being resized. void restoreScrollPosition(); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 417223ae30..4047c533bd 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_inner.h" #include "history/admin_log/history_admin_log_filter.h" +#include "history/history_view_swipe.h" #include "profile/profile_back_button.h" #include "core/shortcuts.h" +#include "ui/chat/chat_style.h" #include "ui/effects/animations.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" @@ -341,6 +343,7 @@ Widget::Widget( }); setupShortcuts(); + setupSwipeReply(); } void Widget::showFilter() { @@ -416,6 +419,37 @@ void Widget::setupShortcuts() { }, lifetime()); } +void Widget::setupSwipeReply() { + HistoryView::SetupSwipeHandler(this, _scroll.data(), [=]( + HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation > 0) { + if (!_swipeBackData.callback) { + _swipeBackData = HistoryView::SetupSwipeBack( + this, + [=]() -> std::pair { + auto context = _inner->preparePaintContext({}); + return { + context.st->msgServiceBg()->c, + context.st->msgServiceFg()->c, + }; + }); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } + }, [=](int, Qt::LayoutDirection direction) { + if (direction == Qt::RightToLeft) { + return HistoryView::SwipeHandlerFinishData{ + .callback = [=] { controller()->showBackFromStack(); }, + .msgBareId = HistoryView::kMsgBareIdSwipeBack, + }; + } + return HistoryView::SwipeHandlerFinishData(); + }, nullptr); +} + std::shared_ptr Widget::createMemento() { auto result = std::make_shared(channel()); saveState(result.get()); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h index 8a547ee817..7521b8f258 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_memento.h" #include "history/admin_log/history_admin_log_item.h" #include "history/admin_log/history_admin_log_filter_value.h" +#include "history/history_view_swipe_data.h" #include "mtproto/sender.h" namespace Ui { @@ -74,6 +75,7 @@ private: void saveState(not_null memento); void restoreState(not_null memento); void setupShortcuts(); + void setupSwipeReply(); object_ptr _scroll; QPointer _inner; @@ -81,6 +83,8 @@ private: object_ptr _fixedBarShadow; object_ptr _whatIsThis; + HistoryView::SwipeBackResult _swipeBackData; + }; class SectionMemento : public Window::SectionMemento { From d662a8f2b939f399194170354a4a0e6d2254970f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 21:17:22 +0300 Subject: [PATCH 007/122] Added ability to swipe-to-back to other sections. --- Telegram/CMakeLists.txt | 2 + .../history_view_swipe_back_session.cpp | 55 +++++++++++++++++++ .../history/history_view_swipe_back_session.h | 30 ++++++++++ .../view/history_view_pinned_section.cpp | 2 + .../view/history_view_scheduled_section.cpp | 2 + .../view/history_view_sublist_section.cpp | 2 + 6 files changed, 93 insertions(+) create mode 100644 Telegram/SourceFiles/history/history_view_swipe_back_session.cpp create mode 100644 Telegram/SourceFiles/history/history_view_swipe_back_session.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 1c881e2ed5..828eedbf1b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -919,6 +919,8 @@ PRIVATE history/history_unread_things.h history/history_view_highlight_manager.cpp history/history_view_highlight_manager.h + history/history_view_swipe_back_session.cpp + history/history_view_swipe_back_session.h history/history_widget.cpp history/history_widget.h info/bot/earn/info_bot_earn_list.cpp diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp new file mode 100644 index 0000000000..179c1bea9a --- /dev/null +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp @@ -0,0 +1,55 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/history_view_swipe_back_session.h" + +#include "history/history_view_swipe_data.h" +#include "history/history_view_swipe.h" +#include "history/view/history_view_list_widget.h" +#include "ui/chat/chat_style.h" +#include "window/window_session_controller.h" + +namespace Window { + +void SetupSwipeBackSection( + not_null parent, + not_null scroll, + not_null list) { + const auto swipeBackData + = list->lifetime().make_state(); + HistoryView::SetupSwipeHandler(parent, scroll, [=]( + HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation > 0) { + if (!swipeBackData->callback) { + const auto color = [=]() -> std::pair { + const auto c = list->delegate()->listPreparePaintContext({ + .theme = list->delegate()->listChatTheme(), + }); + return { + c.st->msgServiceBg()->c, + c.st->msgServiceFg()->c, + }; + }; + (*swipeBackData) = HistoryView::SetupSwipeBack(parent, color); + } + swipeBackData->callback(data); + return; + } else if (swipeBackData->lifetime) { + (*swipeBackData) = {}; + } + }, [=](int, Qt::LayoutDirection direction) { + if (direction != Qt::RightToLeft) { + return HistoryView::SwipeHandlerFinishData(); + } + return HistoryView::SwipeHandlerFinishData{ + .callback = [=] { list->controller()->showBackFromStack(); }, + .msgBareId = HistoryView::kMsgBareIdSwipeBack, + }; + }, list->touchMaybeSelectingValue()); +} + +} // namespace Window diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.h b/Telegram/SourceFiles/history/history_view_swipe_back_session.h new file mode 100644 index 0000000000..8d65f684e9 --- /dev/null +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.h @@ -0,0 +1,30 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class WindowListDelegate; + +namespace HistoryView { +class ListWidget; +} // namespace HistoryView + +namespace Ui { +class RpWidget; +class ScrollArea; +} // namespace Ui + +namespace Window { + +class SectionWidget; + +void SetupSwipeBackSection( + not_null parent, + not_null scroll, + not_null list); + +} // namespace Window diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 7a9ecd8dcc..b8c380c5c1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item_components.h" #include "history/history_item.h" +#include "history/history_view_swipe_back_session.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" @@ -173,6 +174,7 @@ PinnedWidget::PinnedWidget( setupClearButton(); setupTranslateBar(); + Window::SetupSwipeBackSection(this, _scroll.get(), _inner); } PinnedWidget::~PinnedWidget() = default; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 6313d88d0b..729b6f24a2 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_helpers.h" // GetErrorForSending. +#include "history/history_view_swipe_back_session.h" #include "menu/menu_send.h" // SendMenu::Type. #include "ui/widgets/buttons.h" #include "ui/widgets/tooltip.h" @@ -245,6 +246,7 @@ ScheduledWidget::ScheduledWidget( _inner->setEmptyInfoWidget(std::move(emptyInfo)); } setupComposeControls(); + Window::SetupSwipeBackSection(this, _scroll, _inner); } ScheduledWidget::~ScheduledWidget() = default; diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp index 283e3747a5..793133bbd0 100644 --- a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_list_widget.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_view_swipe_back_session.h" #include "lang/lang_keys.h" #include "ui/chat/chat_style.h" #include "ui/widgets/buttons.h" @@ -146,6 +147,7 @@ SublistWidget::SublistWidget( setupShortcuts(); setupTranslateBar(); + Window::SetupSwipeBackSection(this, _scroll.get(), _inner); } SublistWidget::~SublistWidget() = default; From d02e55da062b4f6c2b460944c2c414eff70bda9b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Feb 2025 21:41:25 +0300 Subject: [PATCH 008/122] Added ability to swipe-to-back to all info sections with narrow type. --- .../SourceFiles/info/info_content_widget.cpp | 34 +++++++++++++++++++ .../SourceFiles/info/info_content_widget.h | 4 +++ 2 files changed, 38 insertions(+) diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 9187b0251d..2ae0883469 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/media/info_media_widget.h" #include "info/common_groups/info_common_groups_widget.h" #include "info/info_layer_widget.h" +#include "history/history_view_swipe.h" #include "info/info_section_widget.h" #include "info/info_controller.h" #include "lang/lang_keys.h" @@ -76,6 +77,8 @@ ContentWidget::ContentWidget( ) | rpl::start_with_next([this] { updateControlsGeometry(); }, lifetime()); + + setupSwipeReply(); } void ContentWidget::resizeEvent(QResizeEvent *e) { @@ -381,6 +384,37 @@ not_null ContentWidget::scroll() const { return _scroll.data(); } +void ContentWidget::setupSwipeReply() { + HistoryView::SetupSwipeHandler(this, _scroll.data(), [=]( + HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation > 0) { + if (!_swipeBackData.callback) { + _swipeBackData = HistoryView::SetupSwipeBack( + this, + []() -> std::pair { + return { + st::historyForwardChooseBg->c, + st::historyForwardChooseFg->c, + }; + }); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } + }, [=](int, Qt::LayoutDirection direction) { + if (_controller->wrap() != Wrap::Narrow + || direction != Qt::RightToLeft) { + return HistoryView::SwipeHandlerFinishData(); + } + return HistoryView::SwipeHandlerFinishData{ + .callback = [=] { _controller->showBackFromStack(); }, + .msgBareId = HistoryView::kMsgBareIdSwipeBack, + }; + }, nullptr); +} + Key ContentMemento::key() const { if (const auto topic = this->topic()) { return Key(topic); diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 0fafdfe01c..c53ffadf44 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_wrap_widget.h" #include "info/statistics/info_statistics_tag.h" +#include "history/history_view_swipe_data.h" namespace Api { struct WhoReadList; @@ -167,6 +168,7 @@ private: RpWidget *doSetInnerWidget(object_ptr inner); void updateControlsGeometry(); void refreshSearchField(bool shown); + void setupSwipeReply(); virtual std::shared_ptr doCreateMemento() = 0; @@ -190,6 +192,8 @@ private: // To paint round edges from content. style::margins _paintPadding; + HistoryView::SwipeBackResult _swipeBackData; + }; class ContentMemento { From 6bed3f3f09a8dc86dcb9fbf49cda37ba6e979831 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Feb 2025 00:04:51 +0300 Subject: [PATCH 009/122] Increased speed of swipe-to-back. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 33 +++++++++---------- .../admin_log/history_admin_log_section.cpp | 7 ++-- .../history/history_inner_widget.cpp | 7 ++-- .../history/history_view_swipe.cpp | 24 +++++++++++--- .../SourceFiles/history/history_view_swipe.h | 8 +++-- .../history_view_swipe_back_session.cpp | 7 ++-- .../view/history_view_replies_section.cpp | 7 ++-- .../SourceFiles/info/info_content_widget.cpp | 7 ++-- 8 files changed, 54 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 2a9d17f21e..1ea5ac9b6d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -705,25 +705,22 @@ void Widget::setupSwipeBack() { && !controller()->openedFolder().current())) { return HistoryView::SwipeHandlerFinishData(); } - return HistoryView::SwipeHandlerFinishData{ - .callback = [=] { - _swipeBackData = {}; - if (const auto forum = controller()->shownForum().current()) { - const auto id = controller()->windowId(); - const auto initial = id.forum(); - if (!initial) { - controller()->closeForum(); - } else if (initial != forum) { - controller()->showForum(initial); - } - } else if (controller()->openedFolder().current()) { - if (!controller()->windowId().folder()) { - controller()->closeFolder(); - } + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + _swipeBackData = {}; + if (const auto forum = controller()->shownForum().current()) { + const auto id = controller()->windowId(); + const auto initial = id.forum(); + if (!initial) { + controller()->closeForum(); + } else if (initial != forum) { + controller()->showForum(initial); } - }, - .msgBareId = HistoryView::kMsgBareIdSwipeBack, - }; + } else if (controller()->openedFolder().current()) { + if (!controller()->windowId().folder()) { + controller()->closeFolder(); + } + } + }); }, nullptr); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 4047c533bd..88be0ce7cd 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -441,10 +441,9 @@ void Widget::setupSwipeReply() { } }, [=](int, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { - return HistoryView::SwipeHandlerFinishData{ - .callback = [=] { controller()->showBackFromStack(); }, - .msgBareId = HistoryView::kMsgBareIdSwipeBack, - }; + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + controller()->showBackFromStack(); + }); } return HistoryView::SwipeHandlerFinishData(); }, nullptr); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 6dcf978acd..4114208f91 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -563,10 +563,9 @@ void HistoryInner::setupSwipeReplyAndBack() { int cursorTop, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { - return HistoryView::SwipeHandlerFinishData{ - .callback = [=] { _controller->showBackFromStack(); }, - .msgBareId = HistoryView::kMsgBareIdSwipeBack, - }; + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + _controller->showBackFromStack(); + }); } auto result = HistoryView::SwipeHandlerFinishData(); if (inSelectionMode().inSelectionMode diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/history/history_view_swipe.cpp index 866f25d17d..19f3d66a41 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe.cpp @@ -27,6 +27,9 @@ namespace { constexpr auto kSwipeSlow = 0.2; +constexpr auto kMsgBareIdSwipeBack = std::numeric_limits::max() - 77; +constexpr auto kSwipedBackSpeedRatio = 0.35; + } // namespace void SetupSwipeHandler( @@ -37,7 +40,6 @@ void SetupSwipeHandler( rpl::producer dontStart) { constexpr auto kThresholdWidth = 50; constexpr auto kMaxRatio = 1.5; - const auto threshold = style::ConvertFloatScale(kThresholdWidth); struct UpdateArgs { QPoint globalCursor; QPointF position; @@ -52,6 +54,7 @@ void SetupSwipeHandler( SwipeHandlerFinishData finishByTopData; std::optional orientation; std::optional direction; + float64 threshold = style::ConvertFloatScale(kThresholdWidth); int directionInt = 1.; QPointF startAt; QPointF delta; @@ -75,9 +78,9 @@ void SetupSwipeHandler( state->data.ratio = ratio; const auto overscrollRatio = std::max(ratio - 1., 0.); const auto translation = int( - base::SafeRound(-std::min(ratio, 1.) * threshold) + base::SafeRound(-std::min(ratio, 1.) * state->threshold) ) + Ui::OverscrollFromAccumulated(int( - base::SafeRound(-overscrollRatio * threshold) + base::SafeRound(-overscrollRatio * state->threshold) )); state->data.msgBareId = state->finishByTopData.msgBareId; state->data.translation = translation @@ -102,7 +105,7 @@ void SetupSwipeHandler( if (state->orientation == Qt::Horizontal) { const auto ratio = std::clamp( delta.value_or(state->delta).x() - / threshold + / state->threshold * state->directionInt, 0., kMaxRatio); @@ -159,6 +162,8 @@ void SetupSwipeHandler( state->finishByTopData = generateFinish( state->cursorTop, state->direction.value_or(Qt::RightToLeft)); + state->threshold = style::ConvertFloatScale(kThresholdWidth) + * state->finishByTopData.speedRatio; if (!state->finishByTopData.callback) { setOrientation(Qt::Vertical); } @@ -180,7 +185,7 @@ void SetupSwipeHandler( state->delta = args.delta; const auto ratio = args.delta.x() * state->directionInt - / threshold; + / state->threshold; updateRatio(ratio); constexpr auto kResetReachedOn = 0.95; constexpr auto kBounceDuration = crl::time(500); @@ -414,4 +419,13 @@ SwipeBackResult SetupSwipeBack( return { std::move(lifetime), std::move(callback) }; } +SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( + Fn callback) { + return { + .callback = std::move(callback), + .msgBareId = kMsgBareIdSwipeBack, + .speedRatio = kSwipedBackSpeedRatio, + }; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_view_swipe.h b/Telegram/SourceFiles/history/history_view_swipe.h index 49d1468b10..b4fad89ade 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.h +++ b/Telegram/SourceFiles/history/history_view_swipe.h @@ -18,11 +18,10 @@ namespace HistoryView { struct ChatPaintGestureHorizontalData; struct SwipeBackResult; -constexpr auto kMsgBareIdSwipeBack = std::numeric_limits::max() - 77; - struct SwipeHandlerFinishData { Fn callback; int64 msgBareId = 0; + float64 speedRatio = 1.0; }; using Scroll = std::variant< @@ -37,8 +36,11 @@ void SetupSwipeHandler( Fn generateFinishByTop, rpl::producer dontStart = nullptr); -SwipeBackResult SetupSwipeBack( +[[nodiscard]] SwipeBackResult SetupSwipeBack( not_null widget, Fn()> colors); +[[nodiscard]] SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( + Fn callback); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp index 179c1bea9a..c06cbc4952 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp @@ -45,10 +45,9 @@ void SetupSwipeBackSection( if (direction != Qt::RightToLeft) { return HistoryView::SwipeHandlerFinishData(); } - return HistoryView::SwipeHandlerFinishData{ - .callback = [=] { list->controller()->showBackFromStack(); }, - .msgBareId = HistoryView::kMsgBareIdSwipeBack, - }; + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + list->controller()->showBackFromStack(); + }); }, list->touchMaybeSelectingValue()); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 11e8dfaa54..3fffd0aed6 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -927,10 +927,9 @@ void RepliesWidget::setupSwipeReplyAndBack() { int cursorTop, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { - return HistoryView::SwipeHandlerFinishData{ - .callback = [=] { controller()->showBackFromStack(); }, - .msgBareId = HistoryView::kMsgBareIdSwipeBack, - }; + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + controller()->showBackFromStack(); + }); } auto result = HistoryView::SwipeHandlerFinishData(); if (_inner->elementInSelectionMode(nullptr).inSelectionMode) { diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 2ae0883469..39d9d99d4a 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -408,10 +408,9 @@ void ContentWidget::setupSwipeReply() { || direction != Qt::RightToLeft) { return HistoryView::SwipeHandlerFinishData(); } - return HistoryView::SwipeHandlerFinishData{ - .callback = [=] { _controller->showBackFromStack(); }, - .msgBareId = HistoryView::kMsgBareIdSwipeBack, - }; + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + _controller->showBackFromStack(); + }); }, nullptr); } From ad64e068db185b748a7c59aaf0cfbf46f815a566 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Feb 2025 19:51:27 +0300 Subject: [PATCH 010/122] Allowed swipe-to-back in side info section. --- .../SourceFiles/info/info_content_widget.cpp | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 39d9d99d4a..0ae10c1dd2 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -404,13 +404,24 @@ void ContentWidget::setupSwipeReply() { _swipeBackData = {}; } }, [=](int, Qt::LayoutDirection direction) { - if (_controller->wrap() != Wrap::Narrow - || direction != Qt::RightToLeft) { - return HistoryView::SwipeHandlerFinishData(); - } - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { - _controller->showBackFromStack(); - }); + const auto can = [&] { + using Type = Section::Type; + if (direction != Qt::RightToLeft) { + return false; + } else if (_controller->wrap() == Wrap::Side) { + return (_controller->section().type() != Type::Profile); + } else if (_controller->wrap() == Wrap::Narrow) { + return true; + } else { + return false; + } + }(); + + return !can + ? HistoryView::SwipeHandlerFinishData() + : HistoryView::DefaultSwipeBackHandlerFinishData([=] { + _controller->showBackFromStack(); + }); }, nullptr); } From 4c74cbbbe9e09f82543e90a4b0b09a8543669585 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Feb 2025 21:17:57 +0300 Subject: [PATCH 011/122] Added ability to set swipe back widget on opposite side. --- .../history/history_view_swipe.cpp | 34 ++++++++++++------- .../SourceFiles/history/history_view_swipe.h | 3 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/history/history_view_swipe.cpp index 19f3d66a41..d5b4cee47a 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe.cpp @@ -298,16 +298,17 @@ void SetupSwipeHandler( SwipeBackResult SetupSwipeBack( not_null widget, - Fn()> colors) { + Fn()> colors, + bool mirrored) { struct State { base::unique_qptr back; ChatPaintGestureHorizontalData data; }; - constexpr auto kMaxRightOffset = 0.5; - constexpr auto kMaxLeftOffset = 0.8; + constexpr auto kMaxInnerOffset = 0.5; + constexpr auto kMaxOuterOffset = 0.8; constexpr auto kIdealSize = 100; - const auto maxOffset = st::swipeBackSize * kMaxRightOffset; + const auto maxOffset = st::swipeBackSize * kMaxInnerOffset; const auto sizeRatio = st::swipeBackSize / style::ConvertFloatScale(kIdealSize); @@ -346,10 +347,10 @@ SwipeBackResult SetupSwipeBack( const auto arcRect = rect - Margins(strokeWidth); auto hq = PainterHighQualityEnabler(p); p.setOpacity(ratio); - if (reachScale) { + if (reachScale || mirrored) { const auto scale = (1. + 1. * reachScale); p.translate(center); - p.scale(scale, scale); + p.scale(scale * (mirrored ? -1 : 1), scale); p.translate(-center); } { @@ -405,12 +406,21 @@ SwipeBackResult SetupSwipeBack( raw->show(); raw->raise(); } - state->back->moveToLeft( - anim::interpolate( - -st::swipeBackSize * kMaxLeftOffset, - maxOffset - st::swipeBackSize, - ratio), - (widget->height() - state->back->height()) / 2); + if (!mirrored) { + state->back->moveToLeft( + anim::interpolate( + -st::swipeBackSize * kMaxOuterOffset, + maxOffset - st::swipeBackSize, + ratio), + (widget->height() - state->back->height()) / 2); + } else { + state->back->moveToLeft( + anim::interpolate( + widget->width() + st::swipeBackSize * kMaxOuterOffset, + widget->width() - maxOffset, + ratio), + (widget->height() - state->back->height()) / 2); + } state->back->update(); } else if (state->back) { state->back = nullptr; diff --git a/Telegram/SourceFiles/history/history_view_swipe.h b/Telegram/SourceFiles/history/history_view_swipe.h index b4fad89ade..a6bba34f6e 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.h +++ b/Telegram/SourceFiles/history/history_view_swipe.h @@ -38,7 +38,8 @@ void SetupSwipeHandler( [[nodiscard]] SwipeBackResult SetupSwipeBack( not_null widget, - Fn()> colors); + Fn()> colors, + bool mirrored = false); [[nodiscard]] SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( Fn callback); From acb0b029b9433250e0519c297d01f499648c2789 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Feb 2025 21:43:48 +0300 Subject: [PATCH 012/122] Added ability to switch tab in tabbed selector with swipe. --- .../chat_helpers/tabbed_selector.cpp | 44 +++++++++++++++++++ .../chat_helpers/tabbed_selector.h | 5 +++ 2 files changed, 49 insertions(+) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index f0900e98eb..a212fd4c2d 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" // AllowEmojiWithoutPremium. #include "boxes/premium_preview_box.h" +#include "history/history_view_swipe.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "apiwrap.h" @@ -522,6 +523,7 @@ TabbedSelector::TabbedSelector( if (hasEmojiTab()) { emoji()->refreshEmoji(); } + setupSwipe(); //setAttribute(Qt::WA_AcceptTouchEvents); setAttribute(Qt::WA_OpaquePaintEvent, false); showAll(); @@ -530,6 +532,48 @@ TabbedSelector::TabbedSelector( TabbedSelector::~TabbedSelector() = default; +void TabbedSelector::setupSwipe() { + HistoryView::SetupSwipeHandler(this, _scroll.data(), [=]( + HistoryView::ChatPaintGestureHorizontalData data) { + if (data.translation != 0) { + if (!_swipeBackData.callback) { + _swipeBackData = HistoryView::SetupSwipeBack( + this, + [=]() -> std::pair { + return { + st::historyForwardChooseBg->c, + st::historyForwardChooseFg->c, + }; + }, + data.translation < 0); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } + }, [=](int, Qt::LayoutDirection direction) { + if (!_tabsSlider) { + return HistoryView::SwipeHandlerFinishData(); + } + const auto activeSection = _tabsSlider->activeSection(); + const auto isToLeft = direction == Qt::RightToLeft; + if ((isToLeft && activeSection > 0) + || (!isToLeft && activeSection < _tabs.size() - 1)) { + return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + if (_tabsSlider + && _tabsSlider->activeSection() == activeSection) { + _swipeBackData = {}; + _tabsSlider->setActiveSection(isToLeft + ? activeSection - 1 + : activeSection + 1); + } + }); + } + return HistoryView::SwipeHandlerFinishData(); + }, nullptr); +} + const style::EmojiPan &TabbedSelector::st() const { return _st; } diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index ad8821d846..6670039c38 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/effects/message_sending_animation_common.h" #include "ui/effects/panel_animation.h" +#include "history/history_view_swipe_data.h" #include "ui/cached_round_corners.h" #include "mtproto/sender.h" #include "base/object_ptr.h" @@ -287,12 +288,16 @@ private: not_null gifs() const; not_null masks() const; + void setupSwipe(); + const style::EmojiPan &_st; const ComposeFeatures _features; const std::shared_ptr _show; const PauseReason _level = {}; const Fn _customTextColor; + HistoryView::SwipeBackResult _swipeBackData; + Mode _mode = Mode::Full; int _roundRadius = 0; int _footerTop = 0; From 5cdfaab2db1b0c117e465fe3ec3d27ff5e19d1cd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Feb 2025 00:12:58 +0300 Subject: [PATCH 013/122] Moved out swipe handler from history view to ui controls. --- .../SourceFiles/chat_helpers/tabbed_selector.cpp | 12 ++++++------ Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 10 +++++----- .../admin_log/history_admin_log_section.cpp | 10 +++++----- .../SourceFiles/history/history_inner_widget.cpp | 10 +++++----- .../history/history_view_swipe_back_session.cpp | 12 +++++++----- Telegram/SourceFiles/history/history_widget.cpp | 2 +- .../view/history_view_replies_section.cpp | 10 +++++----- .../SourceFiles/info/info_content_widget.cpp | 10 +++++----- .../controls/swipe_handler.cpp} | 10 +++++++--- .../controls/swipe_handler.h} | 16 +++++++++------- Telegram/cmake/td_ui.cmake | 4 ++-- 11 files changed, 57 insertions(+), 49 deletions(-) rename Telegram/SourceFiles/{history/history_view_swipe.cpp => ui/controls/swipe_handler.cpp} (98%) rename Telegram/SourceFiles/{history/history_view_swipe.h => ui/controls/swipe_handler.h} (85%) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index a212fd4c2d..0e16d66e69 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_list_widget.h" #include "chat_helpers/gifs_list_widget.h" #include "menu/menu_send.h" +#include "ui/controls/swipe_handler.h" #include "ui/controls/tabbed_search.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" @@ -35,7 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" // AllowEmojiWithoutPremium. #include "boxes/premium_preview_box.h" -#include "history/history_view_swipe.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "apiwrap.h" @@ -533,11 +533,11 @@ TabbedSelector::TabbedSelector( TabbedSelector::~TabbedSelector() = default; void TabbedSelector::setupSwipe() { - HistoryView::SetupSwipeHandler(this, _scroll.data(), [=]( + Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation != 0) { if (!_swipeBackData.callback) { - _swipeBackData = HistoryView::SetupSwipeBack( + _swipeBackData = Ui::Controls::SetupSwipeBack( this, [=]() -> std::pair { return { @@ -554,13 +554,13 @@ void TabbedSelector::setupSwipe() { } }, [=](int, Qt::LayoutDirection direction) { if (!_tabsSlider) { - return HistoryView::SwipeHandlerFinishData(); + return Ui::Controls::SwipeHandlerFinishData(); } const auto activeSection = _tabsSlider->activeSection(); const auto isToLeft = direction == Qt::RightToLeft; if ((isToLeft && activeSection > 0) || (!isToLeft && activeSection < _tabs.size() - 1)) { - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { if (_tabsSlider && _tabsSlider->activeSection() == activeSection) { _swipeBackData = {}; @@ -570,7 +570,7 @@ void TabbedSelector::setupSwipe() { } }); } - return HistoryView::SwipeHandlerFinishData(); + return Ui::Controls::SwipeHandlerFinishData(); }, nullptr); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 1ea5ac9b6d..d73544520c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_view_swipe.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_contact_status.h" #include "history/view/history_view_requests_bar.h" @@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/more_chats_bar.h" #include "ui/controls/download_bar.h" #include "ui/controls/jump_down_button.h" +#include "ui/controls/swipe_handler.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/ui_utility.h" @@ -677,11 +677,11 @@ Widget::Widget( } void Widget::setupSwipeBack() { - HistoryView::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( + Ui::Controls::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { - _swipeBackData = HistoryView::SetupSwipeBack( + _swipeBackData = Ui::Controls::SetupSwipeBack( this, []() -> std::pair { return { @@ -703,9 +703,9 @@ void Widget::setupSwipeBack() { || (!controller()->isPrimary() && (_layout != Layout::Child)) || (!controller()->shownForum().current() && !controller()->openedFolder().current())) { - return HistoryView::SwipeHandlerFinishData(); + return Ui::Controls::SwipeHandlerFinishData(); } - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { _swipeBackData = {}; if (const auto forum = controller()->shownForum().current()) { const auto id = controller()->windowId(); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 88be0ce7cd..aabfff4bbd 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -9,10 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_inner.h" #include "history/admin_log/history_admin_log_filter.h" -#include "history/history_view_swipe.h" #include "profile/profile_back_button.h" #include "core/shortcuts.h" #include "ui/chat/chat_style.h" +#include "ui/controls/swipe_handler.h" #include "ui/effects/animations.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" @@ -420,11 +420,11 @@ void Widget::setupShortcuts() { } void Widget::setupSwipeReply() { - HistoryView::SetupSwipeHandler(this, _scroll.data(), [=]( + Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { - _swipeBackData = HistoryView::SetupSwipeBack( + _swipeBackData = Ui::Controls::SetupSwipeBack( this, [=]() -> std::pair { auto context = _inner->preparePaintContext({}); @@ -441,11 +441,11 @@ void Widget::setupSwipeReply() { } }, [=](int, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { controller()->showBackFromStack(); }); } - return HistoryView::SwipeHandlerFinishData(); + return Ui::Controls::SwipeHandlerFinishData(); }, nullptr); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 4114208f91..3af6113bdc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_emoji_interactions.h" #include "history/history_item_components.h" #include "history/history_item_text.h" -#include "history/history_view_swipe.h" #include "payments/payments_reaction_process.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_multiline_action.h" @@ -42,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/edit_factcheck_box.h" #include "ui/boxes/report_box_graphics.h" #include "ui/controls/delete_message_context_action.h" +#include "ui/controls/swipe_handler.h" #include "ui/inactive_press.h" #include "ui/painter.h" #include "ui/rect.h" @@ -528,11 +528,11 @@ void HistoryInner::setupSwipeReplyAndBack() { return; } const auto peer = _peer; - HistoryView::SetupSwipeHandler(this, _scroll, [=, history = _history]( + Ui::Controls::SetupSwipeHandler(this, _scroll, [=, history = _history]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { - _swipeBackData = HistoryView::SetupSwipeBack( + _swipeBackData = Ui::Controls::SetupSwipeBack( _widget, [=]() -> std::pair { auto context = preparePaintContext({}); @@ -563,11 +563,11 @@ void HistoryInner::setupSwipeReplyAndBack() { int cursorTop, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { _controller->showBackFromStack(); }); } - auto result = HistoryView::SwipeHandlerFinishData(); + auto result = Ui::Controls::SwipeHandlerFinishData(); if (inSelectionMode().inSelectionMode || (peer->isChannel() && !peer->isMegagroup())) { return result; diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp index c06cbc4952..9ad05f2867 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp @@ -8,9 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_view_swipe_back_session.h" #include "history/history_view_swipe_data.h" -#include "history/history_view_swipe.h" #include "history/view/history_view_list_widget.h" #include "ui/chat/chat_style.h" +#include "ui/controls/swipe_handler.h" #include "window/window_session_controller.h" namespace Window { @@ -21,7 +21,7 @@ void SetupSwipeBackSection( not_null list) { const auto swipeBackData = list->lifetime().make_state(); - HistoryView::SetupSwipeHandler(parent, scroll, [=]( + Ui::Controls::SetupSwipeHandler(parent, scroll, [=]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation > 0) { if (!swipeBackData->callback) { @@ -34,7 +34,9 @@ void SetupSwipeBackSection( c.st->msgServiceFg()->c, }; }; - (*swipeBackData) = HistoryView::SetupSwipeBack(parent, color); + (*swipeBackData) = Ui::Controls::SetupSwipeBack( + parent, + color); } swipeBackData->callback(data); return; @@ -43,9 +45,9 @@ void SetupSwipeBackSection( } }, [=](int, Qt::LayoutDirection direction) { if (direction != Qt::RightToLeft) { - return HistoryView::SwipeHandlerFinishData(); + return Ui::Controls::SwipeHandlerFinishData(); } - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { list->controller()->showBackFromStack(); }); }, list->touchMaybeSelectingValue()); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5c9f70dbf1..5dbbe25a83 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -52,13 +52,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/send_button.h" #include "ui/controls/send_as_button.h" #include "ui/controls/silent_toggle.h" +#include "ui/controls/swipe_handler.h" #include "ui/ui_utility.h" #include "inline_bots/inline_bot_result.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" #include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" -#include "history/history_view_swipe.h" #include "history/history_view_swipe_data.h" #include "base/call_delayed.h" #include "data/business/data_shortcut_messages.h" diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 3fffd0aed6..a98922ca5d 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -25,9 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_drag_area.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" // GetErrorForSending. -#include "history/history_view_swipe.h" #include "ui/chat/pinned_bar.h" #include "ui/chat/chat_style.h" +#include "ui/controls/swipe_handler.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" @@ -890,11 +890,11 @@ void RepliesWidget::setupSwipeReplyAndBack() { } return false; }; - HistoryView::SetupSwipeHandler(_inner, _scroll.get(), [=]( + Ui::Controls::SetupSwipeHandler(_inner, _scroll.get(), [=]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { - _swipeBackData = HistoryView::SetupSwipeBack( + _swipeBackData = Ui::Controls::SetupSwipeBack( this, [=]() -> std::pair { const auto context = listPreparePaintContext({ @@ -927,11 +927,11 @@ void RepliesWidget::setupSwipeReplyAndBack() { int cursorTop, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { - return HistoryView::DefaultSwipeBackHandlerFinishData([=] { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { controller()->showBackFromStack(); }); } - auto result = HistoryView::SwipeHandlerFinishData(); + auto result = Ui::Controls::SwipeHandlerFinishData(); if (_inner->elementInSelectionMode(nullptr).inSelectionMode) { return result; } diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 0ae10c1dd2..396af38b1f 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -18,11 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/media/info_media_widget.h" #include "info/common_groups/info_common_groups_widget.h" #include "info/info_layer_widget.h" -#include "history/history_view_swipe.h" #include "info/info_section_widget.h" #include "info/info_controller.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/controls/swipe_handler.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/fields/input_field.h" #include "ui/wrap/padding_wrap.h" @@ -385,11 +385,11 @@ not_null ContentWidget::scroll() const { } void ContentWidget::setupSwipeReply() { - HistoryView::SetupSwipeHandler(this, _scroll.data(), [=]( + Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( HistoryView::ChatPaintGestureHorizontalData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { - _swipeBackData = HistoryView::SetupSwipeBack( + _swipeBackData = Ui::Controls::SetupSwipeBack( this, []() -> std::pair { return { @@ -418,8 +418,8 @@ void ContentWidget::setupSwipeReply() { }(); return !can - ? HistoryView::SwipeHandlerFinishData() - : HistoryView::DefaultSwipeBackHandlerFinishData([=] { + ? Ui::Controls::SwipeHandlerFinishData() + : Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { _controller->showBackFromStack(); }); }, nullptr); diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp similarity index 98% rename from Telegram/SourceFiles/history/history_view_swipe.cpp rename to Telegram/SourceFiles/ui/controls/swipe_handler.cpp index d5b4cee47a..2c7f5b0196 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -5,7 +5,7 @@ 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 "history/history_view_swipe.h" +#include "ui/controls/swipe_handler.h" #include "base/platform/base_platform_haptic.h" #include "base/platform/base_platform_info.h" @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include -namespace HistoryView { +namespace Ui::Controls { namespace { constexpr auto kSwipeSlow = 0.2; @@ -30,6 +30,10 @@ constexpr auto kSwipeSlow = 0.2; constexpr auto kMsgBareIdSwipeBack = std::numeric_limits::max() - 77; constexpr auto kSwipedBackSpeedRatio = 0.35; +using ChatPaintGestureHorizontalData + = HistoryView::ChatPaintGestureHorizontalData; +using SwipeBackResult = HistoryView::SwipeBackResult; + } // namespace void SetupSwipeHandler( @@ -438,4 +442,4 @@ SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( }; } -} // namespace HistoryView +} // namespace Ui::Controls diff --git a/Telegram/SourceFiles/history/history_view_swipe.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h similarity index 85% rename from Telegram/SourceFiles/history/history_view_swipe.h rename to Telegram/SourceFiles/ui/controls/swipe_handler.h index a6bba34f6e..92325ec9c5 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -7,16 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace HistoryView { +struct ChatPaintGestureHorizontalData; +struct SwipeBackResult; +} // namespace HistoryView + namespace Ui { class ElasticScroll; class RpWidget; class ScrollArea; } // namespace Ui -namespace HistoryView { - -struct ChatPaintGestureHorizontalData; -struct SwipeBackResult; +namespace Ui::Controls { struct SwipeHandlerFinishData { Fn callback; @@ -32,11 +34,11 @@ using Scroll = std::variant< void SetupSwipeHandler( not_null widget, Scroll scroll, - Fn update, + Fn update, Fn generateFinishByTop, rpl::producer dontStart = nullptr); -[[nodiscard]] SwipeBackResult SetupSwipeBack( +[[nodiscard]] HistoryView::SwipeBackResult SetupSwipeBack( not_null widget, Fn()> colors, bool mirrored = false); @@ -44,4 +46,4 @@ void SetupSwipeHandler( [[nodiscard]] SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( Fn callback); -} // namespace HistoryView +} // namespace Ui::Controls diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 77e710a6a0..8eadb9a9c0 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -129,8 +129,6 @@ PRIVATE history/admin_log/history_admin_log_filter_value.h history/history_view_top_toast.cpp history/history_view_top_toast.h - history/history_view_swipe.cpp - history/history_view_swipe.h history/history_view_swipe_data.h history/view/controls/history_view_characters_limit.cpp history/view/controls/history_view_characters_limit.h @@ -382,6 +380,8 @@ PRIVATE ui/controls/send_as_button.h ui/controls/send_button.cpp ui/controls/send_button.h + ui/controls/swipe_handler.cpp + ui/controls/swipe_handler.h ui/controls/tabbed_search.cpp ui/controls/tabbed_search.h ui/controls/who_reacted_context_action.cpp From de87bed3755bb6a9594ee8fa0417789c5ac052a4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Feb 2025 00:21:05 +0300 Subject: [PATCH 014/122] Moved out data for swipe handler from history view to ui controls. --- .../SourceFiles/chat_helpers/tabbed_selector.cpp | 2 +- .../SourceFiles/chat_helpers/tabbed_selector.h | 4 ++-- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 2 +- Telegram/SourceFiles/dialogs/dialogs_widget.h | 4 ++-- .../admin_log/history_admin_log_section.cpp | 2 +- .../history/admin_log/history_admin_log_section.h | 4 ++-- .../SourceFiles/history/history_inner_widget.cpp | 2 +- .../SourceFiles/history/history_inner_widget.h | 6 +++--- .../history/history_view_swipe_back_session.cpp | 6 +++--- Telegram/SourceFiles/history/history_widget.cpp | 2 +- .../history/view/history_view_replies_section.cpp | 2 +- .../history/view/history_view_replies_section.h | 6 +++--- Telegram/SourceFiles/info/info_content_widget.cpp | 2 +- Telegram/SourceFiles/info/info_content_widget.h | 4 ++-- Telegram/SourceFiles/ui/chat/chat_style.h | 4 ++-- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 14 +++++--------- Telegram/SourceFiles/ui/controls/swipe_handler.h | 12 +++++------- .../controls/swipe_handler_data.h} | 8 ++++---- Telegram/cmake/td_ui.cmake | 2 +- 19 files changed, 41 insertions(+), 47 deletions(-) rename Telegram/SourceFiles/{history/history_view_swipe_data.h => ui/controls/swipe_handler_data.h} (75%) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 0e16d66e69..a2a971e59b 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -534,7 +534,7 @@ TabbedSelector::~TabbedSelector() = default; void TabbedSelector::setupSwipe() { Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation != 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 6670039c38..272cb7f067 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -10,10 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_common.h" #include "chat_helpers/compose/compose_features.h" #include "ui/rp_widget.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/effects/animations.h" #include "ui/effects/message_sending_animation_common.h" #include "ui/effects/panel_animation.h" -#include "history/history_view_swipe_data.h" #include "ui/cached_round_corners.h" #include "mtproto/sender.h" #include "base/object_ptr.h" @@ -296,7 +296,7 @@ private: const PauseReason _level = {}; const Fn _customTextColor; - HistoryView::SwipeBackResult _swipeBackData; + Ui::Controls::SwipeBackResult _swipeBackData; Mode _mode = Mode::Full; int _roundRadius = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index d73544520c..4e157244e5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -678,7 +678,7 @@ Widget::Widget( void Widget::setupSwipeBack() { Ui::Controls::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index a46907ef1d..d726959624 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "dialogs/dialogs_key.h" -#include "history/history_view_swipe_data.h" #include "window/section_widget.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/effects/animations.h" #include "ui/userpic_view.h" #include "mtproto/sender.h" @@ -385,7 +385,7 @@ private: ChatSearchTab _searchQueryTab = {}; ChatTypeFilter _searchQueryFilter = {}; - HistoryView::SwipeBackResult _swipeBackData; + Ui::Controls::SwipeBackResult _swipeBackData; SearchProcessState _searchProcess; SearchProcessState _migratedProcess; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index aabfff4bbd..92af8aec9e 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -421,7 +421,7 @@ void Widget::setupShortcuts() { void Widget::setupSwipeReply() { Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h index 7521b8f258..9643694f82 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_memento.h" #include "history/admin_log/history_admin_log_item.h" #include "history/admin_log/history_admin_log_filter_value.h" -#include "history/history_view_swipe_data.h" +#include "ui/controls/swipe_handler_data.h" #include "mtproto/sender.h" namespace Ui { @@ -83,7 +83,7 @@ private: object_ptr _fixedBarShadow; object_ptr _whatIsThis; - HistoryView::SwipeBackResult _swipeBackData; + Ui::Controls::SwipeBackResult _swipeBackData; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 3af6113bdc..ae56287868 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -529,7 +529,7 @@ void HistoryInner::setupSwipeReplyAndBack() { } const auto peer = _peer; Ui::Controls::SetupSwipeHandler(this, _scroll, [=, history = _history]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 855add11bc..33dd734966 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -10,11 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "data/data_report.h" #include "ui/rp_widget.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/effects/animations.h" #include "ui/dragging_scroll_manager.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/scroll_area.h" -#include "history/history_view_swipe_data.h" #include "history/view/history_view_top_bar_widget.h" #include @@ -543,8 +543,8 @@ private: crl::time _touchTime = 0; base::Timer _touchScrollTimer; - HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; - HistoryView::SwipeBackResult _swipeBackData; + Ui::Controls::SwipeContextData _gestureHorizontal; + Ui::Controls::SwipeBackResult _swipeBackData; // _menu must be destroyed before _whoReactedMenuLifetime. rpl::lifetime _whoReactedMenuLifetime; diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp index 9ad05f2867..e39ee4d4d7 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp @@ -7,10 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_view_swipe_back_session.h" -#include "history/history_view_swipe_data.h" #include "history/view/history_view_list_widget.h" #include "ui/chat/chat_style.h" #include "ui/controls/swipe_handler.h" +#include "ui/controls/swipe_handler_data.h" #include "window/window_session_controller.h" namespace Window { @@ -20,9 +20,9 @@ void SetupSwipeBackSection( not_null scroll, not_null list) { const auto swipeBackData - = list->lifetime().make_state(); + = list->lifetime().make_state(); Ui::Controls::SetupSwipeHandler(parent, scroll, [=]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!swipeBackData->callback) { const auto color = [=]() -> std::pair { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5dbbe25a83..c6c11b082b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -53,13 +53,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/send_as_button.h" #include "ui/controls/silent_toggle.h" #include "ui/controls/swipe_handler.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/ui_utility.h" #include "inline_bots/inline_bot_result.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" #include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" -#include "history/history_view_swipe_data.h" #include "base/call_delayed.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index a98922ca5d..5900e2e6c1 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -891,7 +891,7 @@ void RepliesWidget::setupSwipeReplyAndBack() { return false; }; Ui::Controls::SetupSwipeHandler(_inner, _scroll.get(), [=]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index faab901de2..3bbe7f5c59 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -12,8 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_corner_buttons.h" #include "history/view/history_view_list_widget.h" #include "history/history_item_helpers.h" -#include "history/history_view_swipe_data.h" #include "data/data_messages.h" +#include "ui/controls/swipe_handler_data.h" #include "base/timer.h" class History; @@ -393,8 +393,8 @@ private: HistoryView::CornerButtons _cornerButtons; rpl::lifetime _topicLifetime; - HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; - HistoryView::SwipeBackResult _swipeBackData; + Ui::Controls::SwipeContextData _gestureHorizontal; + Ui::Controls::SwipeBackResult _swipeBackData; SendPaymentHelper _sendPayment; diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 396af38b1f..499ebb16fb 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -386,7 +386,7 @@ not_null ContentWidget::scroll() const { void ContentWidget::setupSwipeReply() { Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( - HistoryView::ChatPaintGestureHorizontalData data) { + Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index c53ffadf44..86389fea99 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_wrap_widget.h" #include "info/statistics/info_statistics_tag.h" -#include "history/history_view_swipe_data.h" +#include "ui/controls/swipe_handler_data.h" namespace Api { struct WhoReadList; @@ -192,7 +192,7 @@ private: // To paint round edges from content. style::margins _paintPadding; - HistoryView::SwipeBackResult _swipeBackData; + Ui::Controls::SwipeBackResult _swipeBackData; }; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 6247d3879e..8205d23049 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/cached_round_corners.h" #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style_radius.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/style/style_core_palette.h" -#include "history/history_view_swipe_data.h" #include "layout/layout_selection.h" #include "styles/style_basic.h" @@ -165,7 +165,7 @@ struct ChatPaintContext { QPainterPath *highlightPathCache = nullptr; mutable QRect highlightInterpolateTo; crl::time now = 0; - HistoryView::ChatPaintGestureHorizontalData gestureHorizontal; + Ui::Controls::SwipeContextData gestureHorizontal; void translate(int x, int y) { viewport.translate(x, y); diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index 2c7f5b0196..e21f07f53e 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -11,8 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/qt/qt_common_adapters.h" #include "base/event_filter.h" -#include "history/history_view_swipe_data.h" #include "ui/chat/chat_style.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/ui_utility.h" @@ -30,16 +30,12 @@ constexpr auto kSwipeSlow = 0.2; constexpr auto kMsgBareIdSwipeBack = std::numeric_limits::max() - 77; constexpr auto kSwipedBackSpeedRatio = 0.35; -using ChatPaintGestureHorizontalData - = HistoryView::ChatPaintGestureHorizontalData; -using SwipeBackResult = HistoryView::SwipeBackResult; - } // namespace void SetupSwipeHandler( not_null widget, Scroll scroll, - Fn update, + Fn update, Fn generateFinish, rpl::producer dontStart) { constexpr auto kThresholdWidth = 50; @@ -54,7 +50,7 @@ void SetupSwipeHandler( base::unique_qptr filter; Ui::Animations::Simple animationReach; Ui::Animations::Simple animationEnd; - ChatPaintGestureHorizontalData data; + SwipeContextData data; SwipeHandlerFinishData finishByTopData; std::optional orientation; std::optional direction; @@ -306,7 +302,7 @@ SwipeBackResult SetupSwipeBack( bool mirrored) { struct State { base::unique_qptr back; - ChatPaintGestureHorizontalData data; + SwipeContextData data; }; constexpr auto kMaxInnerOffset = 0.5; @@ -396,7 +392,7 @@ SwipeBackResult SetupSwipeBack( }; }; - const auto callback = ([=](ChatPaintGestureHorizontalData data) { + const auto callback = ([=](SwipeContextData data) { const auto ratio = std::min(1.0, data.ratio); state->data = std::move(data); if (ratio > 0) { diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index 92325ec9c5..ac3f5f014b 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -7,11 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -namespace HistoryView { -struct ChatPaintGestureHorizontalData; -struct SwipeBackResult; -} // namespace HistoryView - namespace Ui { class ElasticScroll; class RpWidget; @@ -20,6 +15,9 @@ class ScrollArea; namespace Ui::Controls { +struct SwipeContextData; +struct SwipeBackResult; + struct SwipeHandlerFinishData { Fn callback; int64 msgBareId = 0; @@ -34,11 +32,11 @@ using Scroll = std::variant< void SetupSwipeHandler( not_null widget, Scroll scroll, - Fn update, + Fn update, Fn generateFinishByTop, rpl::producer dontStart = nullptr); -[[nodiscard]] HistoryView::SwipeBackResult SetupSwipeBack( +[[nodiscard]] SwipeBackResult SetupSwipeBack( not_null widget, Fn()> colors, bool mirrored = false); diff --git a/Telegram/SourceFiles/history/history_view_swipe_data.h b/Telegram/SourceFiles/ui/controls/swipe_handler_data.h similarity index 75% rename from Telegram/SourceFiles/history/history_view_swipe_data.h rename to Telegram/SourceFiles/ui/controls/swipe_handler_data.h index 3d2d356c4c..b90b5351f5 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_data.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler_data.h @@ -7,9 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -namespace HistoryView { +namespace Ui::Controls { -struct ChatPaintGestureHorizontalData { +struct SwipeContextData final { float64 ratio = 0.; float64 reachRatio = 0.; int64 msgBareId = 0; @@ -19,7 +19,7 @@ struct ChatPaintGestureHorizontalData { struct SwipeBackResult final { rpl::lifetime lifetime; - Fn callback; + Fn callback; }; -} // namespace HistoryView +} // namespace Ui::Controls diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 8eadb9a9c0..321fcbdbc9 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -129,7 +129,6 @@ PRIVATE history/admin_log/history_admin_log_filter_value.h history/history_view_top_toast.cpp history/history_view_top_toast.h - history/history_view_swipe_data.h history/view/controls/history_view_characters_limit.cpp history/view/controls/history_view_characters_limit.h history/view/controls/history_view_voice_record_button.cpp @@ -382,6 +381,7 @@ PRIVATE ui/controls/send_button.h ui/controls/swipe_handler.cpp ui/controls/swipe_handler.h + ui/controls/swipe_handler_data.h ui/controls/tabbed_search.cpp ui/controls/tabbed_search.h ui/controls/who_reacted_context_action.cpp From f0c8e48403e42af1d3bddb61cbb682880acf50e3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Feb 2025 22:29:09 +0300 Subject: [PATCH 015/122] Added ability to keep ratio of swipe within range. --- .../SourceFiles/ui/controls/swipe_handler.cpp | 46 ++++++++++++++++--- .../SourceFiles/ui/controls/swipe_handler.h | 1 + 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index e21f07f53e..654399fc53 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -30,6 +30,33 @@ constexpr auto kSwipeSlow = 0.2; constexpr auto kMsgBareIdSwipeBack = std::numeric_limits::max() - 77; constexpr auto kSwipedBackSpeedRatio = 0.35; +float64 InterpolationRatio(float64 from, float64 to, float64 result) { + return (result - from) / (to - from); +}; + +class RatioRange final { +public: + [[nodiscard]] float64 calcRatio(float64 value) { + if (value < _min) { + const auto shift = _min - value; + _min -= shift; + _max -= shift; + _max = _min + 1; + } else if (value > _max) { + const auto shift = value - _max; + _min += shift; + _max += shift; + _max = _min + 1; + } + return InterpolationRatio(_min, _max, value); + } + +private: + float64 _min = 0; + float64 _max = 1; + +}; + } // namespace void SetupSwipeHandler( @@ -55,6 +82,7 @@ void SetupSwipeHandler( std::optional orientation; std::optional direction; float64 threshold = style::ConvertFloatScale(kThresholdWidth); + RatioRange ratioRange; int directionInt = 1.; QPointF startAt; QPointF delta; @@ -103,10 +131,13 @@ void SetupSwipeHandler( }; const auto processEnd = [=](std::optional delta = {}) { if (state->orientation == Qt::Horizontal) { + const auto rawRatio = delta.value_or(state->delta).x() + / state->threshold + * state->directionInt; const auto ratio = std::clamp( - delta.value_or(state->delta).x() - / state->threshold - * state->directionInt, + state->finishByTopData.keepRatioWithinRange + ? state->ratioRange.calcRatio(rawRatio) + : rawRatio, 0., kMaxRatio); if ((ratio >= 1) && state->finishByTopData.callback) { @@ -183,9 +214,11 @@ void SetupSwipeHandler( } } else if (*state->orientation == Qt::Horizontal) { state->delta = args.delta; - const auto ratio = args.delta.x() - * state->directionInt - / state->threshold; + const auto rawRatio = 0 + + args.delta.x() * state->directionInt / state->threshold; + const auto ratio = state->finishByTopData.keepRatioWithinRange + ? state->ratioRange.calcRatio(rawRatio) + : rawRatio; updateRatio(ratio); constexpr auto kResetReachedOn = 0.95; constexpr auto kBounceDuration = crl::time(500); @@ -435,6 +468,7 @@ SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( .callback = std::move(callback), .msgBareId = kMsgBareIdSwipeBack, .speedRatio = kSwipedBackSpeedRatio, + .keepRatioWithinRange = true, }; } diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index ac3f5f014b..7c5e3ff20f 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -22,6 +22,7 @@ struct SwipeHandlerFinishData { Fn callback; int64 msgBareId = 0; float64 speedRatio = 1.0; + bool keepRatioWithinRange = false; }; using Scroll = std::variant< From f57eff41950ec4f8165a9f9c9fb8c95671cbb5fe Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 8 Mar 2025 04:05:25 +0300 Subject: [PATCH 016/122] Added ability to mirror icon from widget for swipe-to-back. --- .../SourceFiles/ui/controls/swipe_handler.cpp | 22 ++++++++++++------- .../SourceFiles/ui/controls/swipe_handler.h | 3 ++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index 654399fc53..091ebf01e1 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -332,7 +332,8 @@ void SetupSwipeHandler( SwipeBackResult SetupSwipeBack( not_null widget, Fn()> colors, - bool mirrored) { + bool mirrored, + bool iconMirrored) { struct State { base::unique_qptr back; SwipeContextData data; @@ -398,17 +399,22 @@ SwipeBackResult SetupSwipeBack( const auto arrowHalf = arrowSize / 2; const auto arrowX = st::swipeBackSize / 8 + rect.x() - + halfSize - - arrowHalf; + + halfSize; const auto arrowY = rect.y() + halfSize; auto arrowPath = QPainterPath(); - arrowPath.moveTo(arrowX + arrowSize, arrowY); - arrowPath.lineTo(arrowX, arrowY); - arrowPath.lineTo(arrowX + arrowHalf, arrowY - arrowHalf); - arrowPath.moveTo(arrowX, arrowY); - arrowPath.lineTo(arrowX + arrowHalf, arrowY + arrowHalf); + const auto direction = iconMirrored ? -1 : 1; + arrowPath.moveTo(arrowX + direction * arrowSize, arrowY); + arrowPath.lineTo(arrowX, arrowY); + arrowPath.lineTo( + arrowX + direction * arrowHalf, + arrowY - arrowHalf); + arrowPath.moveTo(arrowX, arrowY); + arrowPath.lineTo( + arrowX + direction * arrowHalf, + arrowY + arrowHalf); + arrowPath.translate(-direction * arrowHalf, 0); p.drawPath(arrowPath); } if (reachRatio) { diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index 7c5e3ff20f..744f5f5b3a 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -40,7 +40,8 @@ void SetupSwipeHandler( [[nodiscard]] SwipeBackResult SetupSwipeBack( not_null widget, Fn()> colors, - bool mirrored = false); + bool mirrored = false, + bool iconMirrored = false); [[nodiscard]] SwipeHandlerFinishData DefaultSwipeBackHandlerFinishData( Fn callback); From 3315c9c7f4f6ba220f0257a9644f944a191bbf15 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 8 Mar 2025 04:10:28 +0300 Subject: [PATCH 017/122] Added ability to open main menu with swipe-to-back. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 66 +++++++++++++------ Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 4e157244e5..0cb9f6a065 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -677,6 +677,14 @@ Widget::Widget( } void Widget::setupSwipeBack() { + const auto isMainList = [=] { + const auto current = controller()->activeChatsFilterCurrent(); + const auto &chatsFilters = session().data().chatsFilters(); + if (chatsFilters.has()) { + return chatsFilters.defaultId() == current; + } + return !current; + }; Ui::Controls::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation > 0) { @@ -688,7 +696,9 @@ void Widget::setupSwipeBack() { st::historyForwardChooseBg->c, st::historyForwardChooseFg->c, }; - }); + }, + false, + _swipeBackIconMirrored); } _swipeBackData.callback(data); return; @@ -698,29 +708,45 @@ void Widget::setupSwipeBack() { } } }, [=](int, Qt::LayoutDirection direction) { - if ((direction != Qt::RightToLeft) - || _childListShown.current() - || (!controller()->isPrimary() && (_layout != Layout::Child)) - || (!controller()->shownForum().current() - && !controller()->openedFolder().current())) { + _swipeBackIconMirrored = false; + if (_childListShown.current()) { return Ui::Controls::SwipeHandlerFinishData(); } - return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { - _swipeBackData = {}; - if (const auto forum = controller()->shownForum().current()) { - const auto id = controller()->windowId(); - const auto initial = id.forum(); - if (!initial) { - controller()->closeForum(); - } else if (initial != forum) { - controller()->showForum(initial); - } - } else if (controller()->openedFolder().current()) { - if (!controller()->windowId().folder()) { - controller()->closeFolder(); + const auto isRightToLeft = direction == Qt::RightToLeft; + if (isRightToLeft && controller()->openedFolder().current()) { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + _swipeBackData = {}; + if (controller()->openedFolder().current()) { + if (!controller()->windowId().folder()) { + controller()->closeFolder(); + } } + }); + } + if (isRightToLeft && (controller()->shownForum().current())) { + const auto id = controller()->windowId(); + const auto initial = id.forum(); + if (initial) { + return Ui::Controls::SwipeHandlerFinishData(); } - }); + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + _swipeBackData = {}; + if (const auto forum = controller()->shownForum().current()) { + controller()->closeForum(); + } + }); + } + if (isRightToLeft && isMainList()) { + _swipeBackIconMirrored = true; + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + _swipeBackIconMirrored = false; + _swipeBackData = {}; + if (isMainList()) { + showMainMenu(); + } + }); + } + return Ui::Controls::SwipeHandlerFinishData(); }, nullptr); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index d726959624..b00f814087 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -386,6 +386,7 @@ private: ChatTypeFilter _searchQueryFilter = {}; Ui::Controls::SwipeBackResult _swipeBackData; + bool _swipeBackIconMirrored = false; SearchProcessState _searchProcess; SearchProcessState _migratedProcess; From 7197d9480b2db407d497ef1d2cf6592c0c7157e7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 9 Mar 2025 00:17:25 +0300 Subject: [PATCH 018/122] Added ability to swipe between chats filters. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 26 ++++++++++++++++--- Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 0cb9f6a065..b7bddc5064 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -687,7 +687,7 @@ void Widget::setupSwipeBack() { }; Ui::Controls::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { - if (data.translation > 0) { + if (data.translation != 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( this, @@ -697,7 +697,7 @@ void Widget::setupSwipeBack() { st::historyForwardChooseFg->c, }; }, - false, + _swipeBackMirrored, _swipeBackIconMirrored); } _swipeBackData.callback(data); @@ -709,11 +709,15 @@ void Widget::setupSwipeBack() { } }, [=](int, Qt::LayoutDirection direction) { _swipeBackIconMirrored = false; + _swipeBackMirrored = false; if (_childListShown.current()) { return Ui::Controls::SwipeHandlerFinishData(); } const auto isRightToLeft = direction == Qt::RightToLeft; - if (isRightToLeft && controller()->openedFolder().current()) { + if (controller()->openedFolder().current()) { + if (!isRightToLeft) { + return Ui::Controls::SwipeHandlerFinishData(); + } return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { _swipeBackData = {}; if (controller()->openedFolder().current()) { @@ -723,7 +727,10 @@ void Widget::setupSwipeBack() { } }); } - if (isRightToLeft && (controller()->shownForum().current())) { + if (controller()->shownForum().current()) { + if (!isRightToLeft) { + return Ui::Controls::SwipeHandlerFinishData(); + } const auto id = controller()->windowId(); const auto initial = id.forum(); if (initial) { @@ -746,6 +753,17 @@ void Widget::setupSwipeBack() { } }); } + if (_chatFilters && session().data().chatsFilters().has()) { + _swipeBackMirrored = !isRightToLeft; + using namespace Window; + const auto next = !isRightToLeft; + if (CheckAndJumpToNearChatsFilter(controller(), next, false)) { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + _swipeBackData = {}; + CheckAndJumpToNearChatsFilter(controller(), next, true); + }); + } + } return Ui::Controls::SwipeHandlerFinishData(); }, nullptr); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index b00f814087..698bb56ab1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -386,6 +386,7 @@ private: ChatTypeFilter _searchQueryFilter = {}; Ui::Controls::SwipeBackResult _swipeBackData; + bool _swipeBackMirrored = false; bool _swipeBackIconMirrored = false; SearchProcessState _searchProcess; From 9682e3754707b5deaa93ad08f592a390c4d1365e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 12 Mar 2025 03:43:29 +0300 Subject: [PATCH 019/122] Added shortcut to open chat preview from dialogs. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/core/shortcuts.cpp | 2 ++ Telegram/SourceFiles/core/shortcuts.h | 1 + Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 8 ++++++++ Telegram/SourceFiles/settings/settings_shortcuts.cpp | 1 + 5 files changed, 13 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5f59ce1b35..5d4370b535 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -681,6 +681,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_shortcuts_archive_chat" = "Archive chat"; "lng_shortcuts_media_fullscreen" = "Toggle video fullscreen"; "lng_shortcuts_show_chat_menu" = "Show chat menu"; +"lng_shortcuts_show_chat_preview" = "Show chat preview"; "lng_settings_chat_reactions_title" = "Quick Reaction"; "lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction"; diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index eaad16558a..fe26109b7c 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -110,6 +110,7 @@ const auto CommandByName = base::flat_map{ { u"read_chat"_q , Command::ReadChat }, { u"show_chat_menu"_q , Command::ShowChatMenu }, + { u"show_chat_preview"_q , Command::ShowChatPreview }, // Shortcuts that have no default values. { u"message"_q , Command::JustSendMessage }, @@ -506,6 +507,7 @@ void Manager::fillDefaults() { set(u"ctrl+r"_q, Command::ReadChat); set(u"ctrl+\\"_q, Command::ShowChatMenu); + set(u"ctrl+]"_q, Command::ShowChatPreview); _defaults = keysCurrents(); } diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h index 70c8eb63af..7be77d4eca 100644 --- a/Telegram/SourceFiles/core/shortcuts.h +++ b/Telegram/SourceFiles/core/shortcuts.h @@ -72,6 +72,7 @@ enum class Command { MediaViewerFullscreen, ShowChatMenu, + ShowChatPreview, SupportReloadTemplates, SupportToggleMuted, diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index b7bddc5064..76d8930205 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1314,6 +1314,14 @@ void Widget::setupShortcuts() { } return true; }); + request->check(Command::ShowChatPreview, 1) + && request->handle([=] { + if (_inner) { + Window::ActivateWindow(controller()); + return _inner->showChatPreview(); + } + return true; + }); } }, lifetime()); } diff --git a/Telegram/SourceFiles/settings/settings_shortcuts.cpp b/Telegram/SourceFiles/settings/settings_shortcuts.cpp index 0fd7cf5fb0..9d5135f614 100644 --- a/Telegram/SourceFiles/settings/settings_shortcuts.cpp +++ b/Telegram/SourceFiles/settings/settings_shortcuts.cpp @@ -98,6 +98,7 @@ struct Labeled { { C::ArchiveChat, tr::lng_shortcuts_archive_chat() }, { C::ShowScheduled, tr::lng_shortcuts_scheduled() }, { C::ShowChatMenu, tr::lng_shortcuts_show_chat_menu() }, + { C::ShowChatPreview, tr::lng_shortcuts_show_chat_preview() }, separator, { C::JustSendMessage, tr::lng_shortcuts_just_send() }, { C::SendSilentMessage, tr::lng_shortcuts_silent_send() }, From ff0f7f49dadc7c0c25d89de6b44b82147c97d0fa Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Mar 2025 14:02:04 +0400 Subject: [PATCH 020/122] Fix new swipes on Windows. --- Telegram/SourceFiles/history/history_widget.cpp | 2 -- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 5 +++-- Telegram/build/prepare/prepare.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c6c11b082b..48cecb1301 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -52,8 +52,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/send_button.h" #include "ui/controls/send_as_button.h" #include "ui/controls/silent_toggle.h" -#include "ui/controls/swipe_handler.h" -#include "ui/controls/swipe_handler_data.h" #include "ui/ui_utility.h" #include "inline_bots/inline_bot_result.h" #include "base/event_filter.h" diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index 091ebf01e1..eece262ccc 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -65,8 +65,9 @@ void SetupSwipeHandler( Fn update, Fn generateFinish, rpl::producer dontStart) { - constexpr auto kThresholdWidth = 50; - constexpr auto kMaxRatio = 1.5; + static constexpr auto kThresholdWidth = 50; + static constexpr auto kMaxRatio = 1.5; + struct UpdateArgs { QPoint globalCursor; QPointF position; diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index b9ffb8acd5..86023d1485 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 61bbacab28 + git checkout b88d491492 """) stage('msys64', """ From 0fb67c78a93536656342de458aa328031a09dbb3 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 Mar 2025 05:26:12 +0000 Subject: [PATCH 021/122] Add a method to calculate instance hash to Launcher --- Telegram/SourceFiles/core/launcher.cpp | 16 ++++++++++++ Telegram/SourceFiles/core/launcher.h | 1 + .../platform/linux/specific_linux.cpp | 25 +++++-------------- .../win/windows_app_user_model_id.cpp | 9 +------ 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index 312ce96673..e844acf9ee 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -504,6 +504,22 @@ uint64 Launcher::installationTag() const { return InstallationTag; } +QByteArray Launcher::instanceHash() const { + static const auto Result = [&] { + QByteArray h(32, 0); + if (customWorkingDir()) { + const auto d = QFile::encodeName( + QDir(cWorkingDir()).absolutePath()); + hashMd5Hex(d.constData(), d.size(), h.data()); + } else { + const auto f = QFile::encodeName(cExeDir() + cExeName()); + hashMd5Hex(f.constData(), f.size(), h.data()); + } + return h; + }(); + return Result; +} + void Launcher::processArguments() { enum class KeyFormat { NoValues, diff --git a/Telegram/SourceFiles/core/launcher.h b/Telegram/SourceFiles/core/launcher.h index ea5b1c97f3..e4de3c31d2 100644 --- a/Telegram/SourceFiles/core/launcher.h +++ b/Telegram/SourceFiles/core/launcher.h @@ -33,6 +33,7 @@ public: bool customWorkingDir() const; uint64 installationTag() const; + QByteArray instanceHash() const; bool checkPortableVersionFolder(); bool validateCustomWorkingDir(); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 8eece7d4d7..97255154b1 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -497,10 +497,11 @@ void InstallLauncher() { }); } -[[nodiscard]] QByteArray HashForSocketPath(const QByteArray &data) { +[[nodiscard]] QByteArray HashForSocketPath() { constexpr auto kHashForSocketPathLength = 24; - const auto binary = openssl::Sha256(bytes::make_span(data)); + const auto binary = openssl::Sha256( + bytes::make_span(Core::Launcher::Instance().instanceHash())); const auto base64 = QByteArray( reinterpret_cast(binary.data()), binary.size()).toBase64(QByteArray::Base64UrlEncoding); @@ -656,10 +657,6 @@ int psFixPrevious() { namespace Platform { void start() { - const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); - char h[33] = { 0 }; - hashMd5Hex(d.constData(), d.size(), h); - QGuiApplication::setDesktopFileName([&] { if (KSandbox::isFlatpak()) { return qEnvironmentVariable("FLATPAK_ID"); @@ -672,18 +669,8 @@ void start() { } if (!Core::UpdaterDisabled()) { - QByteArray md5Hash(h); - if (!Core::Launcher::Instance().customWorkingDir()) { - const auto exePath = QFile::encodeName( - cExeDir() + cExeName()); - - hashMd5Hex( - exePath.constData(), - exePath.size(), - md5Hash.data()); - } - - return u"org.telegram.desktop._%1"_q.arg(md5Hash.constData()); + return u"org.telegram.desktop._%1"_q.arg( + Core::Launcher::Instance().instanceHash().constData()); } return u"org.telegram.desktop"_q; @@ -704,7 +691,7 @@ void start() { Webview::WebKitGTK::SetSocketPath(u"%1/%2-%3-webview-%4"_q.arg( QDir::tempPath(), - HashForSocketPath(d), + HashForSocketPath(), u"TD"_q,//QCoreApplication::applicationName(), - make path smaller. u"%1"_q).toStdString()); diff --git a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp index d18e3f9904..3d051f4ce0 100644 --- a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp +++ b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp @@ -493,14 +493,7 @@ const std::wstring &Id() { return BaseId; } static const auto PortableId = [] { - std::string h(32, 0); - if (Core::Launcher::Instance().customWorkingDir()) { - const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); - hashMd5Hex(d.constData(), d.size(), h.data()); - } else { - const auto exePath = QFile::encodeName(cExeDir() + cExeName()); - hashMd5Hex(exePath.constData(), exePath.size(), h.data()); - } + const auto h = Core::Launcher::Instance().instanceHash(); return BaseId + L'.' + std::wstring(h.begin(), h.end()); }(); return PortableId; From 57f10040e1e8c1954ce97a1261d002ccbe519c99 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 Mar 2025 07:13:17 +0000 Subject: [PATCH 022/122] Get rid of XEmbed mentions The code goes through Qt abstractions which not necessarily go through XEmbed --- .../SourceFiles/platform/linux/tray_linux.cpp | 24 +++++++++---------- .../SourceFiles/platform/linux/tray_linux.h | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 2cc92f331a..955aab43a4 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -279,10 +279,10 @@ void Tray::createIcon() { _iconGraphic = std::make_unique(); } - const auto showXEmbed = [=] { + const auto showCustom = [=] { _aboutToShowRequests.fire({}); - InvokeQueued(_menuXEmbed.get(), [=] { - _menuXEmbed->popup(QCursor::pos()); + InvokeQueued(_menuCustom.get(), [=] { + _menuCustom->popup(QCursor::pos()); }); }; @@ -296,7 +296,7 @@ void Tray::createIcon() { &QSystemTrayIcon::activated ) | rpl::start_with_next([=](Reason reason) { if (reason == QSystemTrayIcon::Context) { - showXEmbed(); + showCustom(); } else { _iconClicks.fire({}); } @@ -309,7 +309,7 @@ void Tray::createIcon() { QCoreApplication::instance()); _eventFilter->contextMenuFilters( ) | rpl::start_with_next([=] { - showXEmbed(); + showCustom(); }, _lifetime); } } @@ -337,14 +337,14 @@ void Tray::createMenu() { if (!_menu) { _menu = base::make_unique_q(nullptr); } - if (!_menuXEmbed) { - _menuXEmbed = base::make_unique_q(nullptr); - _menuXEmbed->deleteOnHide(false); + if (!_menuCustom) { + _menuCustom = base::make_unique_q(nullptr); + _menuCustom->deleteOnHide(false); } } void Tray::destroyMenu() { - _menuXEmbed = nullptr; + _menuCustom = nullptr; if (_menu) { _menu->clear(); } @@ -352,12 +352,12 @@ void Tray::destroyMenu() { } void Tray::addAction(rpl::producer text, Fn &&callback) { - if (_menuXEmbed) { - const auto XEAction = _menuXEmbed->addAction(QString(), callback); + if (_menuCustom) { + const auto action = _menuCustom->addAction(QString(), callback); rpl::duplicate( text ) | rpl::start_with_next([=](const QString &text) { - XEAction->setText(text); + action->setText(text); }, _actionsLifetime); } diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.h b/Telegram/SourceFiles/platform/linux/tray_linux.h index 7125927770..181de22b98 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.h +++ b/Telegram/SourceFiles/platform/linux/tray_linux.h @@ -60,7 +60,7 @@ private: base::unique_qptr _icon; base::unique_qptr _menu; - base::unique_qptr _menuXEmbed; + base::unique_qptr _menuCustom; base::unique_qptr _eventFilter; From b14c2878b31814f3f3e803c2561b6987f86eaa2f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 Mar 2025 05:24:55 +0000 Subject: [PATCH 023/122] Revert "Move icon name getter to cross-platform header" This reverts commit d9318c993563b8090a0c0a035bb60de7022adee7. --- Telegram/SourceFiles/core/application.cpp | 2 ++ Telegram/SourceFiles/core/utils.h | 13 ------------- .../platform/linux/notifications_manager_linux.cpp | 7 +++++-- .../SourceFiles/platform/linux/specific_linux.cpp | 13 +++++++++++-- Telegram/SourceFiles/platform/linux/tray_linux.cpp | 2 +- Telegram/SourceFiles/platform/mac/specific_mac.h | 4 ++++ Telegram/SourceFiles/platform/platform_specific.h | 1 + Telegram/SourceFiles/platform/win/specific_win.h | 4 ++++ Telegram/SourceFiles/window/main_window.cpp | 2 +- 9 files changed, 29 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 44dd9df172..f44a792f75 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -93,6 +93,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include + namespace Core { namespace { diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index 325f6ccc43..9d8a3ee622 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -21,10 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#if __has_include() -#include -#endif - #define qsl(s) QStringLiteral(s) namespace base { @@ -34,15 +30,6 @@ inline bool in_range(Value &&value, From &&from, Till &&till) { return (value >= from) && (value < till); } -#if __has_include() -inline QString IconName() { - static const auto Result = KSandbox::isFlatpak() - ? qEnvironmentVariable("FLATPAK_ID") - : u"telegram"_q; - return Result; -} -#endif - inline bool CanReadDirectory(const QString &path) { #ifndef Q_OS_MAC // directory_iterator since 10.15 std::error_code error; diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 47b92fb728..c100e00e06 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/options.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_dbus_utilities.h" +#include "platform/platform_specific.h" #include "core/application.h" #include "core/sandbox.h" #include "core/core_settings.h" @@ -27,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include + #include #include @@ -547,7 +550,7 @@ void Manager::Private::showNotification( notification.set_body(info.message.toStdString()); notification.set_icon( - Gio::ThemedIcon::new_(base::IconName().toStdString())); + Gio::ThemedIcon::new_(ApplicationIconName().toStdString())); // for chat messages, according to // https://docs.gtk.org/gio/enum.NotificationPriority.html @@ -761,7 +764,7 @@ void Manager::Private::showNotification( AppName.data(), 0, (!hasImage - ? base::IconName().toStdString() + ? ApplicationIconName().toStdString() : std::string()).c_str(), (hasBodyMarkup || info.subtitle.isEmpty() ? info.title.toStdString() diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 97255154b1..7a0dd78e9b 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -486,7 +486,7 @@ void InstallLauncher() { if (!QDir(icons).exists()) QDir().mkpath(icons); - const auto icon = icons + base::IconName() + u".png"_q; + const auto icon = icons + ApplicationIconName() + u".png"_q; QFile::remove(icon); if (QFile::copy(u":/gui/art/logo_256.png"_q, icon)) { DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon)); @@ -684,7 +684,9 @@ void start() { } qputenv("PULSE_PROP_application.name", AppName.utf8()); - qputenv("PULSE_PROP_application.icon_name", base::IconName().toLatin1()); + qputenv( + "PULSE_PROP_application.icon_name", + ApplicationIconName().toUtf8()); GLib::set_prgname(cExeName().toStdString()); GLib::set_application_name(AppName.data()); @@ -755,6 +757,13 @@ QImage DefaultApplicationIcon() { return Window::Logo(); } +QString ApplicationIconName() { + static const auto Result = KSandbox::isFlatpak() + ? qEnvironmentVariable("FLATPAK_ID") + : u"telegram"_q; + return Result; +} + namespace ThirdParty { void start() { diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 955aab43a4..f7e91e688a 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -88,7 +88,7 @@ QIcon IconGraphic::systemIcon() const { const auto candidates = { _new.monochrome ? PanelIconName(_new.counter, _new.muted) : QString(), - base::IconName(), + ApplicationIconName(), }; for (const auto &candidate : candidates) { diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 55d29ca4a0..6c95b507b4 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -40,6 +40,10 @@ inline uint64 ActivationWindowId(not_null window) { inline void ActivateOtherProcess(uint64 processId, uint64 windowId) { } +inline QString ApplicationIconName() { + return {}; +} + inline QString ExecutablePathForShortcuts() { return cExeDir() + cExeName(); } diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index 0e423c2292..55561ef2fe 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -47,6 +47,7 @@ void AutostartToggle(bool enabled, Fn done = nullptr); void WriteCrashDumpDetails(); void NewVersionLaunched(int oldVersion); [[nodiscard]] QImage DefaultApplicationIcon(); +[[nodiscard]] QString ApplicationIconName(); [[nodiscard]] bool PreventsQuit(Core::QuitReason reason); [[nodiscard]] QString ExecutablePathForShortcuts(); diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 99ebc36ad3..12a2f4cb5a 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -43,6 +43,10 @@ void SetWindowPriority(not_null window, uint32 priority); // Activate window with windowId (if found) or the largest priority. void ActivateOtherProcess(uint64 processId, uint64 windowId); +inline QString ApplicationIconName() { + return {}; +} + inline QString ExecutablePathForShortcuts() { return cExeDir() + cExeName(); } diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 1db1575b29..81e21fb3c4 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -219,7 +219,7 @@ QIcon CreateIcon(Main::Session *session, bool returnNullIfDefault) { } const auto iconFromTheme = QIcon::fromTheme( - base::IconName(), + Platform::ApplicationIconName(), result); result = QIcon(); From 228bbc1e8e6963708563ff12d2e4b9004bed1b88 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 Mar 2025 05:51:46 +0000 Subject: [PATCH 024/122] Always use app ID based icon name This allows to unify it between flatpak and non-flatpak builds and corresponds to what most other GUI apps on Linux do --- Telegram/CMakeLists.txt | 16 ++++++++-------- .../platform/linux/specific_linux.cpp | 6 +++--- .../SourceFiles/platform/linux/tray_linux.cpp | 8 ++++---- lib/xdg/org.telegram.desktop.desktop | 2 +- snap/snapcraft.yaml | 2 -- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 828eedbf1b..6acfb7d752 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -2063,14 +2063,14 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED) configure_file("../lib/xdg/org.telegram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" @ONLY) generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml") install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}") - install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png") - install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg") + install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-symbolic.svg") install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 7a0dd78e9b..303978af66 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -488,6 +488,7 @@ void InstallLauncher() { const auto icon = icons + ApplicationIconName() + u".png"_q; QFile::remove(icon); + QFile::remove(icons + u"telegram.png"_q); if (QFile::copy(u":/gui/art/logo_256.png"_q, icon)) { DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon)); } @@ -758,9 +759,8 @@ QImage DefaultApplicationIcon() { } QString ApplicationIconName() { - static const auto Result = KSandbox::isFlatpak() - ? qEnvironmentVariable("FLATPAK_ID") - : u"telegram"_q; + static const auto Result = QGuiApplication::desktopFileName().remove( + u"._"_q + Core::Launcher::Instance().instanceHash()); return Result; } diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index f7e91e688a..13b745b1d6 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -30,11 +30,11 @@ namespace { using namespace gi::repository; [[nodiscard]] QString PanelIconName(int counter, bool muted) { - return (counter > 0) + return ApplicationIconName() + ((counter > 0) ? (muted - ? u"telegram-mute-panel"_q - : u"telegram-attention-panel"_q) - : u"telegram-panel"_q; + ? u"-mute"_q + : u"-attention"_q) + : QString()) + u"-panel"_q; } } // namespace diff --git a/lib/xdg/org.telegram.desktop.desktop b/lib/xdg/org.telegram.desktop.desktop index 100752f006..365516b4dd 100644 --- a/lib/xdg/org.telegram.desktop.desktop +++ b/lib/xdg/org.telegram.desktop.desktop @@ -3,7 +3,7 @@ Name=Telegram Comment=New era of messaging TryExec=telegram-desktop Exec=telegram-desktop -- %u -Icon=telegram +Icon=org.telegram.desktop Terminal=false StartupWMClass=TelegramDesktop Type=Application diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 290fbb136a..aeca7ed4a1 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -149,8 +149,6 @@ parts: version="${version}$(git describe --tags | sed 's,^v[^-]\+,,')" craftctl set version="$version" - - sed -i 's|^Icon=telegram$|Icon=${SNAP}/meta/gui/icon.png|g' lib/xdg/org.telegram.desktop.desktop override-build: | craftctl default rm -rf "$CRAFT_PART_INSTALL/usr/share/icons" From 594bb8a76b806e2c92e6c9bc68d79b78319a72da Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 Mar 2025 04:38:42 +0000 Subject: [PATCH 025/122] Support icon themes in snap --- .../SourceFiles/platform/linux/specific_linux.cpp | 4 +++- snap/snapcraft.yaml | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 303978af66..e6bdfe2c14 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -759,7 +759,9 @@ QImage DefaultApplicationIcon() { } QString ApplicationIconName() { - static const auto Result = QGuiApplication::desktopFileName().remove( + static const auto Result = (KSandbox::isSnap() + ? u"snap.%1."_q.arg(qEnvironmentVariable("SNAP_INSTANCE_NAME")) + : QString()) + QGuiApplication::desktopFileName().remove( u"._"_q + Core::Launcher::Instance().instanceHash()); return Result; } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index aeca7ed4a1..7ef0aefda8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,5 @@ name: telegram-desktop adopt-info: telegram -icon: Telegram/Resources/art/icon512@2x.png base: core24 grade: stable @@ -151,7 +150,16 @@ parts: craftctl set version="$version" override-build: | craftctl default - rm -rf "$CRAFT_PART_INSTALL/usr/share/icons" + + APP_ID=org.telegram.desktop + sed -i "s/^Icon=$APP_ID$/Icon=snap.telegram-desktop./g" "$CRAFT_PART_INSTALL/usr/share/applications/$APP_ID.desktop" + for i in $(find "$CRAFT_PART_INSTALL/usr/share/icons" -name "$APP_ID*.svg" -o -name "$APP_ID*.png"); do + PREFIXED="$(dirname "$i")/snap.$CRAFT_PROJECT_NAME.$(basename "$i" | sed "s/$APP_ID//")" + mv "$i" "$PREFIXED" + DEST="$(dirname "$i" | sed 's@usr/share@meta/gui@')" + mkdir -p "$DEST" + cp -r "$PREFIXED" "$DEST" + done after: - ada - ffmpeg From a6a8e32be72b88f73c9e6e89ac643861eaa6b1b4 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 12 Mar 2025 08:30:28 +0000 Subject: [PATCH 026/122] Install app icon in a spec-compilant way --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index e6bdfe2c14..6be12ed24c 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -484,9 +484,10 @@ void InstallLauncher() { const auto icons = QStandardPaths::writableLocation( QStandardPaths::GenericDataLocation) + u"/icons/"_q; - if (!QDir(icons).exists()) QDir().mkpath(icons); + const auto appIcons = icons + u"/hicolor/256x256/apps/"_q; + if (!QDir(appIcons).exists()) QDir().mkpath(appIcons); - const auto icon = icons + ApplicationIconName() + u".png"_q; + const auto icon = appIcons + ApplicationIconName() + u".png"_q; QFile::remove(icon); QFile::remove(icons + u"telegram.png"_q); if (QFile::copy(u":/gui/art/logo_256.png"_q, icon)) { From 31cb2f19996e543ee4a756ba035421bbaf309d6d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 Mar 2025 06:07:56 +0000 Subject: [PATCH 027/122] Switch tray to symbolic icons This allows to rely on GTK recoloring rather than requiring the user to have an icon theme --- Telegram/CMakeLists.txt | 2 ++ .../icons/tray_monochrome_attention.svg | 7 +++++++ .../Resources/icons/tray_monochrome_mute.svg | 7 +++++++ Telegram/Resources/qrc/telegram/telegram.qrc | 2 ++ .../platform/linux/specific_linux.cpp | 19 +++++++++++++++++++ .../SourceFiles/platform/linux/tray_linux.cpp | 2 +- 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Telegram/Resources/icons/tray_monochrome_attention.svg create mode 100644 Telegram/Resources/icons/tray_monochrome_mute.svg diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6acfb7d752..028979462b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -2071,6 +2071,8 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED) install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "org.telegram.desktop.png") install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "org.telegram.desktop.png") install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-symbolic.svg") + install(FILES "Resources/icons/tray_monochrome_attention.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-attention-symbolic.svg") + install(FILES "Resources/icons/tray_monochrome_mute.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-mute-symbolic.svg") install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") diff --git a/Telegram/Resources/icons/tray_monochrome_attention.svg b/Telegram/Resources/icons/tray_monochrome_attention.svg new file mode 100644 index 0000000000..95b8c2cfaf --- /dev/null +++ b/Telegram/Resources/icons/tray_monochrome_attention.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/icons/tray_monochrome_mute.svg b/Telegram/Resources/icons/tray_monochrome_mute.svg new file mode 100644 index 0000000000..448f6990b2 --- /dev/null +++ b/Telegram/Resources/icons/tray_monochrome_mute.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index a5459ef755..e439a4e012 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -24,6 +24,8 @@ ../../icons/settings/star.svg ../../icons/settings/starmini.svg ../../icons/tray_monochrome.svg + ../../icons/tray_monochrome_attention.svg + ../../icons/tray_monochrome_mute.svg ../../art/topic_icons/blue.svg ../../art/topic_icons/yellow.svg ../../art/topic_icons/violet.svg diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6be12ed24c..efe717a556 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -494,6 +494,25 @@ void InstallLauncher() { DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon)); } + const auto symbolicIcons = icons + u"/hicolor/symbolic/apps/"_q; + if (!QDir().exists(symbolicIcons)) QDir().mkpath(symbolicIcons); + + const auto monochromeIcons = { + QString(), + u"attention"_q, + u"mute"_q, + }; + + for (const auto &icon : monochromeIcons) { + QFile::copy( + u":/gui/icons/tray/monochrome%1.svg"_q.arg( + !icon.isEmpty() ? u"_"_q + icon : QString()), + symbolicIcons + + ApplicationIconName() + + (!icon.isEmpty() ? u"-"_q + icon : QString()) + + u"-symbolic.svg"_q); + } + QProcess::execute("update-desktop-database", { applicationsPath }); diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 13b745b1d6..37ef31e259 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -34,7 +34,7 @@ using namespace gi::repository; ? (muted ? u"-mute"_q : u"-attention"_q) - : QString()) + u"-panel"_q; + : QString()) + u"-symbolic"_q; } } // namespace From fd718dfd5c8be017ef89906a859cbeb254128a28 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Mar 2025 18:01:47 +0400 Subject: [PATCH 028/122] Allow more Info sections swipe-back. --- .../SourceFiles/info/info_content_widget.cpp | 26 ++++++------------- Telegram/SourceFiles/info/info_controller.cpp | 4 +++ Telegram/SourceFiles/info/info_controller.h | 1 + Telegram/SourceFiles/info/info_wrap_widget.h | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 499ebb16fb..e0d0cf3eee 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -404,24 +404,14 @@ void ContentWidget::setupSwipeReply() { _swipeBackData = {}; } }, [=](int, Qt::LayoutDirection direction) { - const auto can = [&] { - using Type = Section::Type; - if (direction != Qt::RightToLeft) { - return false; - } else if (_controller->wrap() == Wrap::Side) { - return (_controller->section().type() != Type::Profile); - } else if (_controller->wrap() == Wrap::Narrow) { - return true; - } else { - return false; - } - }(); - - return !can - ? Ui::Controls::SwipeHandlerFinishData() - : Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { - _controller->showBackFromStack(); - }); + return (direction == Qt::RightToLeft && _controller->hasBackButton()) + ? Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + checkBeforeClose(crl::guard(this, [=] { + _controller->parentController()->hideLayer(); + _controller->showBackFromStack(); + })); + }) + : Ui::Controls::SwipeHandlerFinishData(); }, nullptr); } diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index dc51c31e15..b1ec130dc5 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -358,6 +358,10 @@ void Controller::setSection(not_null memento) { updateSearchControllers(memento); } +bool Controller::hasBackButton() const { + return _widget->hasBackButton(); +} + void Controller::updateSearchControllers( not_null memento) { using Type = Section::Type; diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 9815c3bc0d..c82a8b9f5a 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -311,6 +311,7 @@ public: [[nodiscard]] rpl::producer wrapValue() const; [[nodiscard]] not_null wrapWidget() const; void setSection(not_null memento); + [[nodiscard]] bool hasBackButton() const; Ui::SearchFieldController *searchFieldController() const { return _searchFieldController.get(); diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index 815765154a..fe0c4ccd28 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -122,6 +122,7 @@ public: object_ptr createTopBarSurrogate(QWidget *parent); + [[nodiscard]] bool hasBackButton() const; [[nodiscard]] bool closeByOutsideClick() const; void updateGeometry( @@ -181,7 +182,6 @@ private: void highlightTopBar(); void setupShortcuts(); - [[nodiscard]] bool hasBackButton() const; [[nodiscard]] bool willHaveBackButton( const Window::SectionShow ¶ms) const; From 99a1c98ae07df9d383f120cdd26ddb2a31ad8a07 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Mar 2025 18:24:27 +0400 Subject: [PATCH 029/122] Beta version 5.12.4. - Touchpad swipe back to go back in navigation history. - Touchpad swipe between folders if they're horizontal. Out-of-the-box monochrome tray icon on Linux with GTK icon recolorization support. Files telegram{,{,-attention,-mute}-panel} are renamed to org.telegram.desktop{,{,-attention,-mute}-symbolic} for unified icon lookup in multiple (sometimes sandboxed) builds and GTK icon recolorization support. Snap package got icon theming support, the icon names are snap.telegram-desktop.{,{,-attention,-mute}-symbolic} (notice the name ends with a dot in case of no suffix!) --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 10 +++++----- changelog.txt | 7 +++++++ 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 990e326bf2..9d371051cf 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.12.4.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 4821d74720..2298fd13b4 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 5,12,3,0 - PRODUCTVERSION 5,12,3,0 + FILEVERSION 5,12,4,0 + PRODUCTVERSION 5,12,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.12.3.0" + VALUE "FileVersion", "5.12.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.3.0" + VALUE "ProductVersion", "5.12.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index cfe223bde3..0aa5acd9ac 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 5,12,3,0 - PRODUCTVERSION 5,12,3,0 + FILEVERSION 5,12,4,0 + PRODUCTVERSION 5,12,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.12.3.0" + VALUE "FileVersion", "5.12.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.3.0" + VALUE "ProductVersion", "5.12.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index dbe866b0b1..595ae34a97 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 = 5012003; -constexpr auto AppVersionStr = "5.12.3"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 5012004; +constexpr auto AppVersionStr = "5.12.4"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index c1be45cdc7..8ba7a755de 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5012003 +AppVersion 5012004 AppVersionStrMajor 5.12 -AppVersionStrSmall 5.12.3 -AppVersionStr 5.12.3 -BetaChannel 0 +AppVersionStrSmall 5.12.4 +AppVersionStr 5.12.4 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.12.3 +AppVersionOriginal 5.12.4.beta diff --git a/changelog.txt b/changelog.txt index d3d5d42d0d..bf21cce76e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +5.12.4 beta (12.03.25) + +- Touchpad swipe back to go back in navigation history. +- Touchpad swipe between folders if they're horizontal. +- Out-of-the-box monochrome tray icon on Linux. +- (more information about Linux icon changes in the commit message). + 5.12.3 (10.03.25) - Fix a couple more crashes. From 3f6f96cfb4af3b751bb1b3a7d9e275b7acd4d16d Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Mar 2025 21:15:30 +0400 Subject: [PATCH 030/122] Fix typo in lcms2 build commands for Windows. --- Telegram/build/prepare/prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 86023d1485..e14a2e4b02 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1739,7 +1739,7 @@ win: -D WebP_mux_LIBRARY="%WEBP_DIR%\\out\\release-static\\$X8664\\lib\\webpmux.lib" ^ -D LCMS2_FOUND=1 ^ -D LCMS2_INCLUDE_DIR="%LCMS2_DIR%\\include" ^ - -D LCMS2_LIBRARIES="%LCMS2_DIR%\\out\Release\\src\\liblcms2.a" + -D LCMS2_LIBRARIES="%LCMS2_DIR%\\out\\Release\\src\\liblcms2.a" cmake --build . --config Debug --parallel cmake --install . --config Debug From 266043916057cc80f7746af0024db406899c9a4d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 14 Mar 2025 06:10:59 +0000 Subject: [PATCH 031/122] Fix Platform::ApplicationIconName for snap Looks like it broke through rebases --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index efe717a556..dcbefc2ae5 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -779,9 +779,9 @@ QImage DefaultApplicationIcon() { } QString ApplicationIconName() { - static const auto Result = (KSandbox::isSnap() + static const auto Result = KSandbox::isSnap() ? u"snap.%1."_q.arg(qEnvironmentVariable("SNAP_INSTANCE_NAME")) - : QString()) + QGuiApplication::desktopFileName().remove( + : QGuiApplication::desktopFileName().remove( u"._"_q + Core::Launcher::Instance().instanceHash()); return Result; } From 044ef3447c8e89091f1e9d700cd0b32320c58eab Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 14 Mar 2025 07:13:24 +0000 Subject: [PATCH 032/122] Add the missing 5.12.4 changelog part --- changelog.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index bf21cce76e..c2adc06e57 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,7 +3,13 @@ - Touchpad swipe back to go back in navigation history. - Touchpad swipe between folders if they're horizontal. - Out-of-the-box monochrome tray icon on Linux. -- (more information about Linux icon changes in the commit message). +- Icons telegram{,{,-attention,-mute}-panel} are renamed +to org.telegram.desktop{,{,-attention,-mute}-symbolic} for +unified icon lookup in multiple (sometimes sandboxed) builds +and GTK icon recolorization support. +- Snap package got icon theming support, +the icon names are snap.telegram-desktop.{,{,-attention,-mute}-symbolic} +(notice the name ends with a dot in case of no suffix!). 5.12.3 (10.03.25) From 34858b36c1ebfcec897df9f7c78fa350914c4bee Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Mar 2025 14:36:01 +0400 Subject: [PATCH 033/122] Fix swipe gestures on touchscreens. --- .../chat_helpers/tabbed_selector.cpp | 14 ++- .../chat_helpers/tabbed_selector.h | 4 +- .../dialogs/dialogs_inner_widget.cpp | 4 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 4 +- .../admin_log/history_admin_log_section.cpp | 4 +- .../history/history_inner_widget.cpp | 5 + .../history_view_swipe_back_session.cpp | 2 +- .../SourceFiles/info/info_content_widget.cpp | 51 ++++++++-- .../SourceFiles/info/info_content_widget.h | 5 +- .../SourceFiles/ui/controls/swipe_handler.cpp | 93 +++++++++++++------ .../SourceFiles/ui/controls/swipe_handler.h | 3 +- Telegram/lib_ui | 2 +- 12 files changed, 140 insertions(+), 51 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index a2a971e59b..3b73b3d8b0 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -523,8 +523,6 @@ TabbedSelector::TabbedSelector( if (hasEmojiTab()) { emoji()->refreshEmoji(); } - setupSwipe(); - //setAttribute(Qt::WA_AcceptTouchEvents); setAttribute(Qt::WA_OpaquePaintEvent, false); showAll(); hide(); @@ -532,8 +530,10 @@ TabbedSelector::TabbedSelector( TabbedSelector::~TabbedSelector() = default; -void TabbedSelector::setupSwipe() { - Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( +void TabbedSelector::reinstallSwipe(not_null widget) { + _swipeLifetime.destroy(); + + Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation != 0) { if (!_swipeBackData.callback) { @@ -571,7 +571,7 @@ void TabbedSelector::setupSwipe() { }); } return Ui::Controls::SwipeHandlerFinishData(); - }, nullptr); + }, nullptr, &_swipeLifetime); } const style::EmojiPan &TabbedSelector::st() const { @@ -1345,6 +1345,10 @@ void TabbedSelector::setWidgetToScrollArea() { inner->moveToLeft(0, 0); inner->show(); + if (_tabs.size() > 1) { + reinstallSwipe(inner); + } + _scroll->disableScroll(false); scrollToY(currentTab()->getScrollTop()); handleScroll(); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 272cb7f067..47e8c4b649 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -288,7 +288,7 @@ private: not_null gifs() const; not_null masks() const; - void setupSwipe(); + void reinstallSwipe(not_null widget); const style::EmojiPan &_st; const ComposeFeatures _features; @@ -334,6 +334,8 @@ private: rpl::event_stream<> _showRequests; rpl::event_stream<> _slideFinished; + rpl::lifetime _swipeLifetime; + }; class TabbedSelector::Inner : public Ui::RpWidget { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 39e047897d..5836207a1f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2972,7 +2972,7 @@ bool InnerWidget::processTouchEvent(not_null e) { } if (_chatPreviewTouchGlobal) { const auto delta = (*_chatPreviewTouchGlobal - *point); - if (delta.manhattanLength() > _st->photoSize) { + if (delta.manhattanLength() >= QApplication::startDragDistance()) { cancelChatPreview(); } } @@ -2981,7 +2981,7 @@ bool InnerWidget::processTouchEvent(not_null e) { return _dragging != nullptr; } else if (_touchDragStartGlobal) { const auto delta = (*_touchDragStartGlobal - *point); - if (delta.manhattanLength() > QApplication::startDragDistance()) { + if (delta.manhattanLength() >= QApplication::startDragDistance()) { if (_touchDragPinnedTimer.isActive()) { _touchDragPinnedTimer.cancel(); _touchDragStartGlobal = {}; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 76d8930205..83ab9665e9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -685,7 +685,7 @@ void Widget::setupSwipeBack() { } return !current; }; - Ui::Controls::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=]( + Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation != 0) { if (!_swipeBackData.callback) { @@ -765,7 +765,7 @@ void Widget::setupSwipeBack() { } } return Ui::Controls::SwipeHandlerFinishData(); - }, nullptr); + }); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 92af8aec9e..ef88f0b384 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -420,7 +420,7 @@ void Widget::setupShortcuts() { } void Widget::setupSwipeReply() { - Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( + Ui::Controls::SetupSwipeHandler(_inner.data(), _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { @@ -446,7 +446,7 @@ void Widget::setupSwipeReply() { }); } return Ui::Controls::SwipeHandlerFinishData(); - }, nullptr); + }); } std::shared_ptr Widget::createMemento() { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index ae56287868..643a59998c 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1525,6 +1525,11 @@ void HistoryInner::touchEvent(QTouchEvent *e) { } break; case QEvent::TouchUpdate: { + LOG(("UPDATE: %1,%2 -> %3,%4" + ).arg(_touchStart.x() + ).arg(_touchStart.y() + ).arg(_touchPos.x() + ).arg(_touchPos.y())); if (!_touchInProgress) { return; } else if (_touchSelect) { diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp index e39ee4d4d7..05dfa6df6c 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp @@ -21,7 +21,7 @@ void SetupSwipeBackSection( not_null list) { const auto swipeBackData = list->lifetime().make_state(); - Ui::Controls::SetupSwipeHandler(parent, scroll, [=]( + Ui::Controls::SetupSwipeHandler(list, scroll, [=]( Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!swipeBackData->callback) { diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index e0d0cf3eee..a6396b329d 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -77,8 +77,6 @@ ContentWidget::ContentWidget( ) | rpl::start_with_next([this] { updateControlsGeometry(); }, lifetime()); - - setupSwipeReply(); } void ContentWidget::resizeEvent(QResizeEvent *e) { @@ -157,6 +155,17 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget( object_ptr inner) { using namespace rpl::mappers; + const auto tmp = new Ui::RpWidget(this); + tmp->raise(); + tmp->show(); + tmp->setAttribute(Qt::WA_TransparentForMouseEvents); + tmp->paintRequest() | rpl::start_with_next([=] { + QPainter(tmp).fillRect(tmp->rect(), QColor(255, 0, 0, 64)); + }, tmp->lifetime()); + _scroll->geometryValue() | rpl::start_with_next([=] { + tmp->setGeometry(_scroll->geometry()); + }, tmp->lifetime()); + _innerWrap = _scroll->setOwnedWidget( object_ptr>( this, @@ -164,6 +173,8 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget( _innerWrap ? _innerWrap->padding() : style::margins())); _innerWrap->move(0, 0); + setupSwipeHandler(_innerWrap); + // MSVC BUG + REGRESSION rpl::mappers::tuple :( rpl::combine( _scroll->scrollTopValue(), @@ -179,6 +190,24 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget( _scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0)); }, _innerWrap->lifetime()); + rpl::combine( + _scroll->heightValue(), + _innerWrap->entity()->heightValue(), + _controller->wrapValue() + ) | rpl::start_with_next([=]( + int scrollHeight, + int innerHeight, + Wrap wrap) { + const auto added = (wrap == Wrap::Layer) + ? 0 + : std::max(scrollHeight - innerHeight, 0); + if (_addedHeight != added) { + _addedHeight = added; + updateInnerPadding(); + } + }, _innerWrap->lifetime()); + updateInnerPadding(); + return _innerWrap->entity(); } @@ -208,11 +237,19 @@ rpl::producer ContentWidget::scrollHeightValue() const { } void ContentWidget::applyAdditionalScroll(int additionalScroll) { - if (_innerWrap) { - _innerWrap->setPadding({ 0, 0, 0, additionalScroll }); + if (_additionalScroll != additionalScroll) { + _additionalScroll = additionalScroll; + if (_innerWrap) { + updateInnerPadding(); + } } } +void ContentWidget::updateInnerPadding() { + const auto addedToBottom = std::max(_additionalScroll, _addedHeight); + _innerWrap->setPadding({ 0, 0, 0, addedToBottom }); +} + void ContentWidget::applyMaxVisibleHeight(int maxVisibleHeight) { if (_maxVisibleHeight != maxVisibleHeight) { _maxVisibleHeight = maxVisibleHeight; @@ -384,8 +421,8 @@ not_null ContentWidget::scroll() const { return _scroll.data(); } -void ContentWidget::setupSwipeReply() { - Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=]( +void ContentWidget::setupSwipeHandler(not_null widget) { + Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { @@ -412,7 +449,7 @@ void ContentWidget::setupSwipeReply() { })); }) : Ui::Controls::SwipeHandlerFinishData(); - }, nullptr); + }); } Key ContentMemento::key() const { diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 86389fea99..84b03d2934 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -168,7 +168,8 @@ private: RpWidget *doSetInnerWidget(object_ptr inner); void updateControlsGeometry(); void refreshSearchField(bool shown); - void setupSwipeReply(); + void setupSwipeHandler(not_null widget); + void updateInnerPadding(); virtual std::shared_ptr doCreateMemento() = 0; @@ -183,6 +184,8 @@ private: base::unique_qptr _searchWrap = nullptr; QPointer _searchField; int _innerDesiredHeight = 0; + int _additionalScroll = 0; + int _addedHeight = 0; int _maxVisibleHeight = 0; bool _isStackBottom = false; diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index eece262ccc..3ec1f32ca6 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/swipe_handler.h" +#include "base/debug_log.h" + #include "base/platform/base_platform_haptic.h" #include "base/platform/base_platform_info.h" #include "base/qt/qt_common_adapters.h" @@ -64,7 +66,8 @@ void SetupSwipeHandler( Scroll scroll, Fn update, Fn generateFinish, - rpl::producer dontStart) { + rpl::producer dontStart, + rpl::lifetime *onLifetime) { static constexpr auto kThresholdWidth = 50; static constexpr auto kMaxRatio = 1.5; @@ -95,12 +98,23 @@ void SetupSwipeHandler( rpl::lifetime lifetime; }; - const auto state = widget->lifetime().make_state(); - std::move( - dontStart - ) | rpl::start_with_next([=](bool dontStart) { - state->dontStart = dontStart; - }, state->lifetime); + auto &useLifetime = onLifetime ? *onLifetime : widget->lifetime(); + const auto state = useLifetime.make_state(); + if (dontStart) { + std::move( + dontStart + ) | rpl::start_with_next([=](bool dontStart) { + state->dontStart = dontStart; + }, state->lifetime); + } else { + v::match(scroll, [](v::null_t) { + }, [&](const auto &scroll) { + scroll->touchMaybePressing( + ) | rpl::start_with_next([=](bool maybePressing) { + state->dontStart = maybePressing; + }, state->lifetime); + }); + } const auto updateRatio = [=](float64 ratio) { ratio = std::max(ratio, 0.); @@ -123,9 +137,11 @@ void SetupSwipeHandler( v::match(scroll, [](v::null_t) { }, [&](const auto &scroll) { if (const auto viewport = scroll->viewport()) { - viewport->setAttribute( - Qt::WA_AcceptTouchEvents, - !isHorizontal); + if (viewport != widget) { + viewport->setAttribute( + Qt::WA_AcceptTouchEvents, + !isHorizontal); + } } scroll->disableScroll(isHorizontal); }); @@ -173,37 +189,52 @@ void SetupSwipeHandler( update(state->data); }; const auto updateWith = [=](UpdateArgs args) { - if (!state->started - || state->touch != args.touch - || !state->direction) { - state->direction = (args.delta.x() == 0) - ? std::nullopt - : args.delta.x() < 0 - ? std::make_optional(Qt::RightToLeft) - : std::make_optional(Qt::LeftToRight); - state->directionInt = (!state->direction - || (*state->direction) == Qt::LeftToRight) + const auto fillFinishByTop = [&] { + if (!args.delta.x()) { + LOG(("SKIPPING fillFinishByTop.")); + return; + } + LOG(("SETTING DIRECTION")); + state->direction = (args.delta.x() < 0) + ? Qt::RightToLeft + : Qt::LeftToRight; + state->directionInt = (state->direction == Qt::LeftToRight) ? 1 : -1; - - state->started = true; - state->touch = args.touch; - state->startAt = args.position; - state->delta = QPointF(); - state->cursorTop = widget->mapFromGlobal(args.globalCursor).y(); state->finishByTopData = generateFinish( state->cursorTop, - state->direction.value_or(Qt::RightToLeft)); + *state->direction); state->threshold = style::ConvertFloatScale(kThresholdWidth) * state->finishByTopData.speedRatio; if (!state->finishByTopData.callback) { setOrientation(Qt::Vertical); } + }; + if (!state->started || state->touch != args.touch) { + LOG(("STARTING")); + state->started = true; + state->touch = args.touch; + state->startAt = args.position; + state->cursorTop = widget->mapFromGlobal(args.globalCursor).y(); + if (!state->touch) { + // args.delta already is valid. + fillFinishByTop(); + } else { + // args.delta depends on state->startAt, so it's invalid. + state->direction = std::nullopt; + } + state->delta = QPointF(); + } else if (!state->direction) { + fillFinishByTop(); } else if (!state->orientation) { state->delta = args.delta; const auto diffXtoY = std::abs(args.delta.x()) - std::abs(args.delta.y()); constexpr auto kOrientationThreshold = 1.; + LOG(("SETTING ORIENTATION WITH: %1,%2, diff %3" + ).arg(args.delta.x() + ).arg(args.delta.y() + ).arg(diffXtoY)); if (diffXtoY > kOrientationThreshold) { if (!state->dontStart) { setOrientation(Qt::Horizontal); @@ -239,6 +270,9 @@ void SetupSwipeHandler( } }; const auto filter = [=](not_null e) { + if (!widget->testAttribute(Qt::WA_AcceptTouchEvents)) { + [[maybe_unused]] int a = 0; + } const auto type = e->type(); switch (type) { case QEvent::Leave: { @@ -262,7 +296,7 @@ void SetupSwipeHandler( const auto t = static_cast(e.get()); const auto touchscreen = t->device() && (t->device()->type() == base::TouchDevice::TouchScreen); - if (!touchscreen) { + if (!touchscreen && type != QEvent::TouchCancel) { break; } else if (type == QEvent::TouchBegin) { // Reset state in case we lost some TouchEnd. @@ -292,8 +326,10 @@ void SetupSwipeHandler( .delta = state->startAt - touches[0].pos(), .touch = true, }; + LOG(("ORIENTATION UPDATING WITH: %1, %2").arg(args.delta.x()).arg(args.delta.y())); updateWith(args); } + LOG(("ORIENTATION: %1").arg(!state->orientation ? "none" : (state->orientation == Qt::Horizontal) ? "horizontal" : "vertical")); return (touchscreen && state->orientation != Qt::Horizontal) ? base::EventFilterResult::Continue : base::EventFilterResult::Cancel; @@ -326,6 +362,7 @@ void SetupSwipeHandler( } return base::EventFilterResult::Continue; }; + widget->setAttribute(Qt::WA_AcceptTouchEvents); state->filter = base::make_unique_q( base::install_event_filter(widget, filter)); } diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index 744f5f5b3a..79413091eb 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -35,7 +35,8 @@ void SetupSwipeHandler( Scroll scroll, Fn update, Fn generateFinishByTop, - rpl::producer dontStart = nullptr); + rpl::producer dontStart = nullptr, + rpl::lifetime *onLifetime = nullptr); [[nodiscard]] SwipeBackResult SetupSwipeBack( not_null widget, diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ba96966730..107729e446 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ba969667301ae4d8da2c2f6c4528bea63443f607 +Subproject commit 107729e446e8a2037ceb68374f90978fec937504 From 188edce25864ee63e475576a6e303a40de4eb57c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Mar 2025 15:02:53 +0400 Subject: [PATCH 034/122] Support swipe-back from main menu. --- .../SourceFiles/window/window_main_menu.cpp | 74 +++++++++++++++++++ .../SourceFiles/window/window_main_menu.h | 14 ++-- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 61157fe228..d035668295 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_main_menu.h" #include "apiwrap.h" +#include "base/event_filter.h" #include "base/qt_signal_producer.h" #include "boxes/about_box.h" #include "boxes/peer_list_controllers.h" @@ -39,12 +40,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_templates.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/chat_theme.h" +#include "ui/controls/swipe_handler.h" #include "ui/controls/userpic_button.h" #include "ui/effects/snowflakes.h" #include "ui/effects/toggle_arrow.h" #include "ui/painter.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" +#include "ui/ui_utility.h" #include "ui/unread_badge_paint.h" #include "ui/vertical_list.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" @@ -532,6 +535,8 @@ MainMenu::MainMenu( } }, lifetime()); } + + setupSwipe(); } MainMenu::~MainMenu() = default; @@ -896,6 +901,19 @@ void MainMenu::chooseEmojiStatus() { } } +bool MainMenu::eventHook(QEvent *event) { + const auto type = event->type(); + if (type == QEvent::TouchBegin + || type == QEvent::TouchUpdate + || type == QEvent::TouchEnd + || type == QEvent::TouchCancel) { + QTouchEvent ev = *static_cast(event); + ev.setTimestamp(crl::now()); + QGuiApplication::sendEvent(_inner, &ev); + } + return RpWidget::eventHook(event); +} + void MainMenu::paintEvent(QPaintEvent *e) { auto p = Painter(this); const auto clip = e->rect(); @@ -996,4 +1014,60 @@ rpl::producer OtherAccountsUnreadState( }); } +void MainMenu::setupSwipe() { + const auto outer = _controller->widget()->body(); + base::install_event_filter(this, outer, [=](not_null e) { + const auto type = e->type(); + if (type == QEvent::TouchBegin + || type == QEvent::TouchUpdate + || type == QEvent::TouchEnd + || type == QEvent::TouchCancel) { + QGuiApplication::sendEvent(_inner, e); + return base::EventFilterResult::Cancel; + } else if (type == QEvent::Wheel) { + const auto w = static_cast(e.get()); + const auto d = Ui::ScrollDeltaF(w); + if (std::abs(d.x()) > std::abs(d.y())) { + QGuiApplication::sendEvent(_inner, e); + return base::EventFilterResult::Cancel; + } + } + return base::EventFilterResult::Continue; + }); + const auto handles = outer->testAttribute(Qt::WA_AcceptTouchEvents); + if (!handles) { + outer->setAttribute(Qt::WA_AcceptTouchEvents); + lifetime().add([=] { + outer->setAttribute(Qt::WA_AcceptTouchEvents, false); + }); + } + + Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=]( + Ui::Controls::SwipeContextData data) { + if (data.translation < 0) { + if (!_swipeBackData.callback) { + _swipeBackData = Ui::Controls::SetupSwipeBack( + this, + [=]() -> std::pair { + return { + st::historyForwardChooseBg->c, + st::historyForwardChooseFg->c, + }; + }); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } + }, [=](int, Qt::LayoutDirection direction) { + if (direction != Qt::LeftToRight) { + return Ui::Controls::SwipeHandlerFinishData(); + } + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + closeLayer(); + }); + }); +} + } // namespace Window diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index 3089151e4f..764803fd0d 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/binary_guard.h" #include "ui/rp_widget.h" #include "ui/unread_badge.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/layers/layer_widget.h" namespace Ui { @@ -49,7 +50,11 @@ public: void parentResized() override; void showFinished() override; -protected: +private: + class ToggleAccountsButton; + class ResetScaleButton; + + bool eventHook(QEvent *event) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; @@ -57,10 +62,6 @@ protected: setFocus(); } -private: - class ToggleAccountsButton; - class ResetScaleButton; - void moveBadge(); void setupUserpicButton(); void setupAccounts(); @@ -73,6 +74,7 @@ private: void initResetScaleButton(); void toggleAccounts(); void chooseEmojiStatus(); + void setupSwipe(); void drawName(Painter &p); @@ -99,6 +101,8 @@ private: base::Timer _nightThemeSwitch; base::unique_qptr _contextMenu; + Ui::Controls::SwipeBackResult _swipeBackData; + rpl::variable _showFinished = false; }; From 033cb2790c4981415b301faf6b30ff4d9aa979fd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Mar 2025 16:03:58 +0400 Subject: [PATCH 035/122] Remove debug widget in the info section. --- Telegram/SourceFiles/info/info_content_widget.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index a6396b329d..b7078a984b 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -155,17 +155,6 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget( object_ptr inner) { using namespace rpl::mappers; - const auto tmp = new Ui::RpWidget(this); - tmp->raise(); - tmp->show(); - tmp->setAttribute(Qt::WA_TransparentForMouseEvents); - tmp->paintRequest() | rpl::start_with_next([=] { - QPainter(tmp).fillRect(tmp->rect(), QColor(255, 0, 0, 64)); - }, tmp->lifetime()); - _scroll->geometryValue() | rpl::start_with_next([=] { - tmp->setGeometry(_scroll->geometry()); - }, tmp->lifetime()); - _innerWrap = _scroll->setOwnedWidget( object_ptr>( this, From abe8079296d6061143ff4e4576538a3b6bf1b9d7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 12:09:51 +0400 Subject: [PATCH 036/122] Allow temp-set of wearable as a status. --- Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 51d2882349..dde73d671a 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -1261,7 +1261,7 @@ void EmojiListWidget::fillEmojiStatusMenu( int section, int index) { const auto chosen = lookupCustomEmoji(index, section); - if (!chosen || chosen.collectible) { + if (!chosen) { return; } const auto selectWith = [=](TimeId scheduled) { From d1e4dbb603f01ec7d0456989d3fb9213f2651036 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 12:22:19 +0400 Subject: [PATCH 037/122] Fix animated emoji in folder name. --- Telegram/SourceFiles/boxes/choose_filter_box.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 93164126bd..6630c0428b 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -295,6 +295,7 @@ void FillChooseFilterMenu( contains ? &st::mediaPlayerMenuCheck : nullptr); item->setMarkedText(title.text, QString(), Core::TextContext({ .session = &history->session(), + .repaint = [raw = item.get()] { raw->update(); }, .customEmojiLoopLimit = title.isStatic ? -1 : 0, })); From 06ed6c81a7ce91e20aa7c75b864fe930f671385e Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 12:23:23 +0400 Subject: [PATCH 038/122] Remove 'delete also for' in Deleted Accounts. --- Telegram/SourceFiles/boxes/delete_messages_box.cpp | 3 ++- Telegram/SourceFiles/data/data_peer.cpp | 1 + Telegram/SourceFiles/history/history_item.cpp | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 247cad699a..d880ab5860 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -271,7 +271,8 @@ void DeleteMessagesBox::prepare() { appendDetails({ tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count) }); - } else if (!peer->isSelf()) { + } else if (!peer->isSelf() + && (!peer->isUser() || !peer->asUser()->isInaccessible())) { if (const auto user = peer->asUser(); user && user->isBot()) { _revokeForBot = true; } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 5c7536083f..20fdd10737 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1483,6 +1483,7 @@ bool PeerData::canRevokeFullHistory() const { if (const auto user = asUser()) { return !isSelf() && (!user->isBot() || user->isSupport()) + && !user->isInaccessible() && session().serverConfig().revokePrivateInbox && (session().serverConfig().revokePrivateTimeLimit == 0x7FFFFFFF); } else if (const auto chat = asChat()) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 08afc9a2e4..1153cce27b 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2413,7 +2413,8 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const { } else if (const auto user = peer->asUser()) { // Bots receive all messages and there is no sense in revoking them. // See https://github.com/telegramdesktop/tdesktop/issues/3818 - if (user->isBot() && !user->isSupport()) { + if ((user->isBot() && !user->isSupport()) + || user->isInaccessible()) { return false; } } From b3e5c4a4aa532375d93d227b4d729071785226e0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 12:27:04 +0400 Subject: [PATCH 039/122] Fix send button size on chat switch. --- Telegram/SourceFiles/ui/controls/send_button.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index 14e8e11928..b02bf1c6c7 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -78,6 +78,7 @@ void SendButton::setState(State state) { void SendButton::finishAnimating() { _stateChangeAnimation.stop(); + updateSize(); update(); } From f9df522b419bfa11cd9e2e5d354c84dee88ad809 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 13:30:05 +0400 Subject: [PATCH 040/122] Show error not-found on comment link click. --- Telegram/SourceFiles/window/window_session_controller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 6b8abba7af..a3fdd4d6d4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1095,6 +1095,8 @@ void SessionNavigation::showRepliesForMessage( if (error.type() == u"CHANNEL_PRIVATE"_q || error.type() == u"USER_BANNED_IN_CHANNEL"_q) { showToast(tr::lng_group_not_accessible(tr::now)); + } else if (error.type() == u"MSG_ID_INVALID"_q) { + showToast(tr::lng_message_not_found(tr::now)); } }).send(); } From 19e2642ec1c5a190cf82bb34c82099df0d14f840 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 13:35:50 +0400 Subject: [PATCH 041/122] Improve paid messages in groups layout. --- .../history/view/history_view_bottom_info.cpp | 14 +++++++++----- Telegram/SourceFiles/ui/effects/credits.style | 4 ++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index c5fde09fd9..e93a5c6faa 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "ui/painter.h" +#include "core/ui_integration.h" #include "lang/lang_keys.h" #include "history/history_item_components.h" #include "history/history_item.h" @@ -433,15 +434,18 @@ void BottomInfo::layoutDateText() { : name.isEmpty() ? date : (name + afterAuthor); - auto marked = TextWithEntities{ full }; + auto marked = TextWithEntities(); if (const auto count = _data.stars) { - marked.append(Ui::Text::IconEmoji(&st::starIconEmoji)); - marked.append(Lang::FormatCountToShort(count).string); + marked.append( + Ui::Text::IconEmoji(&st::starIconEmojiSmall) + ).append(Lang::FormatCountToShort(count).string).append(u", "_q); } + marked.append(full); _authorEditedDate.setMarkedText( st::msgDateTextStyle, marked, - Ui::NameTextOptions()); + Ui::NameTextOptions(), + Core::TextContext({ .session = &_reactionsOwner->session() })); } void BottomInfo::layoutViewsText() { @@ -606,7 +610,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { if (item->isSending() || item->hasFailed()) { result.flags |= Flag::Sending; } - if (item->out() && !item->history()->peer->isUser()) { + if (!item->history()->peer->isUser()) { const auto media = message->media(); const auto mine = PaidInformation{ .messages = 1, diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index e9b20d5bb4..906f29c7da 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -69,6 +69,10 @@ starIconEmoji: IconEmoji { starIconEmojiColored: IconEmoji(starIconEmoji) { useIconColor: true; } +starIconEmojiSmall: IconEmoji { + icon: icon{{ "chat/mini_stars", creditsBg1 }}; + padding: margins(0px, 4px, 0px, 0px); +} creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }}; From 9d2e2a1739454fa9b7a5499a336737b1102d2e61 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 13:53:09 +0400 Subject: [PATCH 042/122] Fix processing of video qualities. Regression was introduced in b618d1e56a. --- Telegram/SourceFiles/history/history_item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 1153cce27b..a6870a81be 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -304,7 +304,7 @@ std::unique_ptr HistoryItem::CreateMedia( return document->match([&](const MTPDdocument &document) -> Result { const auto list = media.valt_documents(); const auto owner = &item->history()->owner(); - const auto data = owner->processDocument(document); + const auto data = owner->processDocument(document, list); using Args = Data::MediaFile::Args; return std::make_unique(item, data, Args{ .ttlSeconds = media.vttl_seconds().value_or_empty(), From 6d5d61c842000169c037b9bdb04d88de5e4ab5e9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 16:28:17 +0400 Subject: [PATCH 043/122] Label 'Add users' for Remove Fee. --- Telegram/SourceFiles/boxes/edit_privacy_box.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 5c74632618..c3c92325a0 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -1054,7 +1054,7 @@ void EditMessagesPrivacyBox( tr::now, lt_count, always) - : QString(); + : tr::lng_edit_privacy_exceptions_add(tr::now); }); const auto exceptions = Settings::AddButtonWithLabel( From 8e6d23ddd6fb6c66396f5de8056f9d8b1067b66b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Mar 2025 16:29:21 +0400 Subject: [PATCH 044/122] Support tg://settings/phone_privacy links. --- .../SourceFiles/core/local_url_handlers.cpp | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 8c5b3b7d2a..66ff7370a4 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -302,6 +302,42 @@ void ShowLanguagesBox(Window::SessionController *controller) { Guard = LanguageBox::Show(controller); } +void ShowPhonePrivacyBox(Window::SessionController *controller) { + static auto Guard = base::binary_guard(); + auto guard = base::binary_guard(); + + using Privacy = Api::UserPrivacy; + const auto key = Privacy::Key::PhoneNumber; + controller->session().api().userPrivacy().reload(key); + + const auto weak = base::make_weak(controller); + auto shared = std::make_shared( + guard.make_guard()); + auto lifetime = std::make_shared(); + controller->session().api().userPrivacy().value( + key + ) | rpl::take( + 1 + ) | rpl::start_with_next([=](const Privacy::Rule &value) mutable { + using namespace ::Settings; + const auto show = shared->alive(); + if (lifetime) { + base::take(lifetime)->destroy(); + } + if (show) { + if (const auto controller = weak.get()) { + controller->show(Box( + controller, + std::make_unique( + controller), + value)); + } + } + }, *lifetime); + + Guard = std::move(guard); +} + bool SetLanguage( Window::SessionController *controller, const Match &match, @@ -718,6 +754,9 @@ bool ResolveSettings( if (section == u"language"_q) { ShowLanguagesBox(controller); return {}; + } else if (section == u"phone_privacy"_q) { + ShowPhonePrivacyBox(controller); + return {}; } else if (section == u"devices"_q) { return ::Settings::Sessions::Id(); } else if (section == u"folders"_q) { @@ -1462,7 +1501,7 @@ const std::vector &LocalUrlHandlers() { ResolvePrivatePost }, { - u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile)?$"_q, + u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile|/phone_privacy)?$"_q, ResolveSettings }, { From 5b257293ebe0b40acb5ed0aa4413564886e5a326 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 14 Mar 2025 10:30:12 +0000 Subject: [PATCH 045/122] Update patches on Linux --- Telegram/build/docker/centos_env/Dockerfile | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 48c5f3ccd6..4e2c8f6fbe 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -44,7 +44,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin 61bbacab28d377d0a2771cc87e008554e2f23ffb \ + && git fetch --depth=1 origin fb35ec726cf0a99eead201ddf80e1e717b9fa7c2 \ && git reset --hard FETCH_HEAD \ && rm -rf .git diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 7ef0aefda8..cb8f1ea108 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -171,7 +171,7 @@ parts: patches: source: https://github.com/desktop-app/patches.git source-depth: 1 - source-commit: 61bbacab28d377d0a2771cc87e008554e2f23ffb + source-commit: fb35ec726cf0a99eead201ddf80e1e717b9fa7c2 plugin: dump override-pull: | craftctl default From 532258bea84b682d8e9521587466abf3143ca615 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Mar 2025 17:06:07 +0400 Subject: [PATCH 046/122] Beta version 5.12.5. - Support swipe navigation on touchscreens. - Close main menu by swipe navigation. --- 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 | 5 +++++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 9d371051cf..3367d09c3d 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.12.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 2298fd13b4..a5ae1ef3cc 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 5,12,4,0 - PRODUCTVERSION 5,12,4,0 + FILEVERSION 5,12,5,0 + PRODUCTVERSION 5,12,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.12.4.0" + VALUE "FileVersion", "5.12.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.4.0" + VALUE "ProductVersion", "5.12.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0aa5acd9ac..39c1ad93af 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 5,12,4,0 - PRODUCTVERSION 5,12,4,0 + FILEVERSION 5,12,5,0 + PRODUCTVERSION 5,12,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.12.4.0" + VALUE "FileVersion", "5.12.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.4.0" + VALUE "ProductVersion", "5.12.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 595ae34a97..562c1004a9 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 = 5012004; -constexpr auto AppVersionStr = "5.12.4"; +constexpr auto AppVersion = 5012005; +constexpr auto AppVersionStr = "5.12.5"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 8ba7a755de..c614f7e0ae 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5012004 +AppVersion 5012005 AppVersionStrMajor 5.12 -AppVersionStrSmall 5.12.4 -AppVersionStr 5.12.4 +AppVersionStrSmall 5.12.5 +AppVersionStr 5.12.5 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.12.4.beta +AppVersionOriginal 5.12.5.beta diff --git a/changelog.txt b/changelog.txt index c2adc06e57..74d420c7eb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +5.12.5 beta (14.03.25) + +- Support swipe navigation on touchscreens. +- Close main menu by swipe navigation. + 5.12.4 beta (12.03.25) - Touchpad swipe back to go back in navigation history. From af35beefc2b532efcdaaff83c3cf7dd438bb62ea Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Mar 2025 17:23:54 +0400 Subject: [PATCH 047/122] Beta version 5.12.5: Fix build with Qt 6. --- Telegram/SourceFiles/window/window_main_menu.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index d035668295..f61b217a24 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -907,9 +907,7 @@ bool MainMenu::eventHook(QEvent *event) { || type == QEvent::TouchUpdate || type == QEvent::TouchEnd || type == QEvent::TouchCancel) { - QTouchEvent ev = *static_cast(event); - ev.setTimestamp(crl::now()); - QGuiApplication::sendEvent(_inner, &ev); + QGuiApplication::sendEvent(_inner, event); } return RpWidget::eventHook(event); } From fe0c1acd793c2a38946979072000ef2723a906ff Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Mar 2025 10:45:23 +0400 Subject: [PATCH 048/122] Fix crash in main menu swipes on macOS. --- .../SourceFiles/window/window_main_menu.cpp | 19 +++++++++++++++---- .../SourceFiles/window/window_main_menu.h | 8 ++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index f61b217a24..b4344d2023 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -1012,6 +1012,19 @@ rpl::producer OtherAccountsUnreadState( }); } +base::EventFilterResult MainMenu::redirectToInnerChecked(not_null e) { + if (_insideEventRedirect) { + return base::EventFilterResult::Continue; + } + const auto weak = Ui::MakeWeak(this); + _insideEventRedirect = true; + QGuiApplication::sendEvent(_inner, e); + if (weak) { + _insideEventRedirect = false; + } + return base::EventFilterResult::Cancel; +} + void MainMenu::setupSwipe() { const auto outer = _controller->widget()->body(); base::install_event_filter(this, outer, [=](not_null e) { @@ -1020,14 +1033,12 @@ void MainMenu::setupSwipe() { || type == QEvent::TouchUpdate || type == QEvent::TouchEnd || type == QEvent::TouchCancel) { - QGuiApplication::sendEvent(_inner, e); - return base::EventFilterResult::Cancel; + return redirectToInnerChecked(e); } else if (type == QEvent::Wheel) { const auto w = static_cast(e.get()); const auto d = Ui::ScrollDeltaF(w); if (std::abs(d.x()) > std::abs(d.y())) { - QGuiApplication::sendEvent(_inner, e); - return base::EventFilterResult::Cancel; + return redirectToInnerChecked(e); } } return base::EventFilterResult::Continue; diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index 764803fd0d..36a3087a05 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/swipe_handler_data.h" #include "ui/layers/layer_widget.h" +namespace base { +enum class EventFilterResult; +} // namespace base + namespace Ui { class IconButton; class FlatLabel; @@ -76,6 +80,9 @@ private: void chooseEmojiStatus(); void setupSwipe(); + [[nodiscard]] base::EventFilterResult redirectToInnerChecked( + not_null e); + void drawName(Painter &p); const not_null _controller; @@ -104,6 +111,7 @@ private: Ui::Controls::SwipeBackResult _swipeBackData; rpl::variable _showFinished = false; + bool _insideEventRedirect = false; }; From 0d346610a2f535d9edd8df1a2c52f04e3203802a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Mar 2025 10:35:15 +0300 Subject: [PATCH 049/122] Fixed sort of received star gifts. --- Telegram/SourceFiles/api/api_premium.cpp | 1 + Telegram/SourceFiles/boxes/star_gift_box.cpp | 15 ++------------- Telegram/SourceFiles/data/data_star_gift.h | 1 + 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index d7dbcd37f1..61769069fa 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -816,6 +816,7 @@ std::optional FromTL( .lastSaleDate = data.vlast_sale_date().value_or_empty(), .upgradable = data.vupgrade_stars().has_value(), .birthday = data.is_birthday(), + .soldOut = data.is_sold_out(), }); }, [&](const MTPDstarGiftUnique &data) { const auto total = data.vavailability_total().v; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index bc15e90593..cb3fdf2520 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -718,19 +718,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { for (auto &gift : gifts) { list.push_back({ .info = gift }); } - ranges::sort(list, []( - const GiftTypeStars &a, - const GiftTypeStars &b) { - if (!a.info.limitedCount && !b.info.limitedCount) { - return a.info.stars <= b.info.stars; - } else if (!a.info.limitedCount) { - return true; - } else if (!b.info.limitedCount) { - return false; - } else if (a.info.limitedLeft != b.info.limitedLeft) { - return a.info.limitedLeft > b.info.limitedLeft; - } - return a.info.stars <= b.info.stars; + ranges::stable_sort(list, [](const auto &a, const auto &b) { + return a.info.soldOut < b.info.soldOut; }); auto &map = Map[session]; diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index 1b9a8ca063..c529be692e 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -69,6 +69,7 @@ struct StarGift { TimeId lastSaleDate = 0; bool upgradable = false; bool birthday = false; + bool soldOut = false; friend inline bool operator==( const StarGift &, From eb4ef8b3d791dbfb0274244098126f4915b53768 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Mar 2025 14:19:43 +0400 Subject: [PATCH 050/122] Replace Reply with Forward for specific bots. --- .../history/view/history_view_element.cpp | 8 -- .../history/view/history_view_element.h | 2 - .../history/view/history_view_message.cpp | 76 ++++++++++++++++--- .../history/view/history_view_message.h | 6 +- 4 files changed, 70 insertions(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index e78b6c65db..e830ee5dbc 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1549,14 +1549,6 @@ bool Element::unwrapped() const { return true; } -bool Element::hasFastReply() const { - return false; -} - -bool Element::displayFastReply() const { - return false; -} - std::optional Element::rightActionSize() const { return std::nullopt; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index de00c0f48d..af62b3f3b6 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -484,8 +484,6 @@ public: [[nodiscard]] virtual int minWidthForMedia() const { return 0; } - [[nodiscard]] virtual bool hasFastReply() const; - [[nodiscard]] virtual bool displayFastReply() const; [[nodiscard]] virtual std::optional rightActionSize() const; virtual void drawRightAction( Painter &p, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 5e8d1c05b7..591e8b37d4 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -334,10 +334,19 @@ int KeyboardStyle::minButtonWidth( return result; } +QString FastForwardText() { + return u"Forward"_q; +} + QString FastReplyText() { return tr::lng_fast_reply(tr::now); } +bool ShowFastForwardFor(const QString &username) { + return !username.compare(u"ReviewInsightsBot"_q, Qt::CaseInsensitive) + || !username.compare(u"reviews_bot"_q, Qt::CaseInsensitive); +} + [[nodiscard]] ClickHandlerPtr MakeTopicButtonLink( not_null topic, MsgId messageId) { @@ -966,7 +975,9 @@ QSize Message::performCountOptimalSize() { namew += st::msgServiceFont->spacew + via->maxWidth + (_fromNameStatus ? st::msgServiceFont->spacew : 0); } - const auto replyWidth = hasFastReply() + const auto replyWidth = hasFastForward() + ? st::msgFont->width(FastForwardText()) + : hasFastReply() ? st::msgFont->width(FastReplyText()) : 0; if (!_rightBadge.isEmpty()) { @@ -1783,8 +1794,12 @@ void Message::paintFromName( } const auto badgeWidth = _rightBadge.isEmpty() ? 0 : _rightBadge.maxWidth(); const auto replyWidth = [&] { - if (isUnderCursor() && displayFastReply()) { - return st::msgFont->width(FastReplyText()); + if (isUnderCursor()) { + if (displayFastForward()) { + return st::msgFont->width(FastForwardText()); + } else if (displayFastReply()) { + return st::msgFont->width(FastReplyText()); + } } return 0; }(); @@ -1883,7 +1898,7 @@ void Message::paintFromName( p.drawText( trect.left() + trect.width() - rightWidth, trect.top() + st::msgFont->ascent, - FastReplyText()); + hasFastForward() ? FastForwardText() : FastReplyText()); } else { const auto shift = QPoint(trect.width() - rightWidth, 0); const auto pen = !_rightBadgeHasBoosts @@ -2796,8 +2811,12 @@ bool Message::getStateFromName( return false; } const auto replyWidth = [&] { - if (isUnderCursor() && displayFastReply()) { - return st::msgFont->width(FastReplyText()); + if (isUnderCursor()) { + if (displayFastForward()) { + return st::msgFont->width(FastForwardText()); + } else if (displayFastReply()) { + return st::msgFont->width(FastReplyText()); + } } return 0; }(); @@ -3838,6 +3857,22 @@ bool Message::hasFastReply() const { return !hasOutLayout() && (peer->isChat() || peer->isMegagroup()); } +bool Message::hasFastForward() const { + if (context() != Context::History) { + return false; + } + const auto item = data(); + const auto from = item->from()->asUser(); + if (!from || !from->isBot() || !ShowFastForwardFor(from->username())) { + return false; + } + const auto peer = item->history()->peer; + if (!peer->isChat() && !peer->isMegagroup()) { + return false; + } + return !hasOutLayout(); +} + bool Message::displayFastReply() const { const auto canSendAnything = [&] { const auto item = data(); @@ -3854,6 +3889,12 @@ bool Message::displayFastReply() const { && !delegate()->elementInSelectionMode(this).inSelectionMode; } +bool Message::displayFastForward() const { + return hasFastForward() + && data()->allowsForward() + && !delegate()->elementInSelectionMode(this).inSelectionMode; +} + bool Message::displayRightActionComments() const { return !isPinnedContext() && (context() != Context::SavedSublist) @@ -4122,9 +4163,22 @@ ClickHandlerPtr Message::fastReplyLink() const { return _fastReplyLink; } const auto itemId = data()->fullId(); - _fastReplyLink = std::make_shared([=] { - delegate()->elementReplyTo({ itemId }); - }); + const auto sessionId = data()->history()->session().uniqueId(); + _fastReplyLink = hasFastForward() + ? std::make_shared([=](ClickContext context) { + const auto controller = ExtractController(context); + const auto session = controller + ? &controller->session() + : nullptr; + if (!session || session->uniqueId() != sessionId) { + return; + } else if (const auto item = session->data().message(itemId)) { + FastShareMessage(controller, item); + } + }) + : std::make_shared(crl::guard(this, [=] { + delegate()->elementReplyTo({ itemId }); + })); return _fastReplyLink; } @@ -4213,7 +4267,9 @@ void Message::updateMediaInBubbleState() { void Message::fromNameUpdated(int width) const { const auto item = data(); - const auto replyWidth = hasFastReply() + const auto replyWidth = hasFastForward() + ? st::msgFont->width(FastForwardText()) + : hasFastReply() ? st::msgFont->width(FastReplyText()) : 0; if (!_rightBadge.isEmpty()) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index b33b29edc8..a68c5fa971 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -129,8 +129,6 @@ public: TopicButton *displayedTopicButton() const override; bool unwrapped() const override; int minWidthForMedia() const override; - bool hasFastReply() const override; - bool displayFastReply() const override; bool displayRightActionComments() const; std::optional rightActionSize() const override; void drawRightAction( @@ -275,6 +273,10 @@ private: [[nodiscard]] int visibleMediaTextLength() const; [[nodiscard]] bool needInfoDisplay() const; [[nodiscard]] bool invertMedia() const; + [[nodiscard]] bool hasFastReply() const; + [[nodiscard]] bool hasFastForward() const; + [[nodiscard]] bool displayFastReply() const; + [[nodiscard]] bool displayFastForward() const; [[nodiscard]] bool isPinnedContext() const; From 7123a6d6472dc9bccfceb09eca211f211703cf62 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Mar 2025 16:01:16 +0400 Subject: [PATCH 051/122] Hide image preview of ttl rounds behind spoiler. --- Telegram/SourceFiles/data/data_media_types.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 02c1299872..7223c6dbcc 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1101,7 +1101,9 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { auto images = std::vector(); auto context = std::any(); const auto existing = options.existing; - if (auto found = FindCachedPreview(existing, _document, _spoiler)) { + const auto spoilered = _spoiler + || (_document->isVideoMessage() && ttlSeconds()); + if (auto found = FindCachedPreview(existing, _document, spoilered)) { images.push_back(std::move(found)); } else if (TryFilePreview(_document)) { const auto media = _document->createMediaView(); @@ -1112,7 +1114,7 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { parent(), media, radius, - _spoiler) + spoilered) ; prepared || !prepared.cacheKey) { images.push_back(std::move(prepared)); if (!prepared.cacheKey) { From bb8ecf2f84bb4e335a1e02070aeee01d87835183 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 16:55:12 +0400 Subject: [PATCH 052/122] Fix spoiler hiding of ellipse image preview. --- .../SourceFiles/data/data_media_types.cpp | 80 +++++++++++++------ .../dialogs/ui/dialogs_message_view.cpp | 13 ++- .../dialogs/ui/dialogs_message_view.h | 1 + .../history/view/history_view_item_preview.h | 3 + 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 7223c6dbcc..97ef0e6d65 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -171,8 +171,19 @@ struct AlbumCounts { } template -[[nodiscard]] uint64 CountCacheKey(not_null data, bool spoiler) { - return (reinterpret_cast(data.get()) & ~1) | (spoiler ? 1 : 0); +[[nodiscard]] uint64 CountCacheKey( + not_null data, + ImageRoundRadius radius, + bool spoiler) { + return (reinterpret_cast(data.get()) & ~3) + | ((radius == ImageRoundRadius::Ellipse) ? 2 : 0) + | (spoiler ? 1 : 0); +} + +[[nodiscard]] uint64 SimpleCacheKey(ImageRoundRadius radius, bool spoiler) { + return uint64() + | ((radius == ImageRoundRadius::Ellipse) ? 2 : 0) + | (spoiler ? 1 : 0); } [[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage( @@ -181,7 +192,7 @@ template ImageRoundRadius radius, bool spoiler) { const auto photo = media->owner(); - const auto counted = CountCacheKey(photo, spoiler); + const auto counted = CountCacheKey(photo, radius, spoiler); if (const auto small = media->image(PhotoSize::Small)) { return { PreparePreviewImage(small, radius, spoiler), counted }; } else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) { @@ -191,15 +202,15 @@ template } const auto allowedToDownload = media->autoLoadThumbnailAllowed( item->history()->peer); - const auto spoilered = uint64(spoiler ? 1 : 0); - const auto cacheKey = allowedToDownload ? spoilered : counted; + const auto simple = SimpleCacheKey(radius, spoiler); + const auto cacheKey = allowedToDownload ? simple : counted; if (allowedToDownload) { media->owner()->load(PhotoSize::Small, item->fullId()); } if (const auto blurred = media->thumbnailInline()) { return { PreparePreviewImage(blurred, radius, spoiler), cacheKey }; } - return { QImage(), allowedToDownload ? spoilered : cacheKey }; + return { QImage(), allowedToDownload ? simple : cacheKey }; } [[nodiscard]] ItemPreviewImage PrepareFilePreviewImage( @@ -210,7 +221,7 @@ template Expects(media->owner()->hasThumbnail()); const auto document = media->owner(); - const auto readyCacheKey = CountCacheKey(document, spoiler); + const auto readyCacheKey = CountCacheKey(document, radius, spoiler); if (const auto thumbnail = media->thumbnail()) { return { PreparePreviewImage(thumbnail, radius, spoiler), @@ -218,11 +229,11 @@ template }; } document->loadThumbnail(item->fullId()); - const auto spoilered = uint64(spoiler ? 1 : 0); + const auto simple = SimpleCacheKey(radius, spoiler); if (const auto blurred = media->thumbnailInline()) { - return { PreparePreviewImage(blurred, radius, spoiler), spoilered }; + return { PreparePreviewImage(blurred, radius, spoiler), simple }; } - return { QImage(), spoilered }; + return { QImage(), simple }; } [[nodiscard]] QImage PutPlayIcon(QImage preview) { @@ -274,13 +285,14 @@ template [[nodiscard]] ItemPreviewImage FindCachedPreview( const std::vector *existing, not_null data, + ImageRoundRadius radius, bool spoiler) { if (!existing) { return {}; } const auto i = ranges::find( *existing, - CountCacheKey(data, spoiler), + CountCacheKey(data, radius, spoiler), &ItemPreviewImage::cacheKey); return (i != end(*existing)) ? *i : ItemPreviewImage(); } @@ -856,13 +868,17 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const { } auto images = std::vector(); auto context = std::any(); - if (auto found = FindCachedPreview(options.existing, _photo, _spoiler)) { + const auto radius = _chat + ? ImageRoundRadius::Ellipse + : ImageRoundRadius::Small; + if (auto found = FindCachedPreview( + options.existing, + _photo, + radius, + _spoiler)) { images.push_back(std::move(found)); } else { const auto media = _photo->createMediaView(); - const auto radius = _chat - ? ImageRoundRadius::Ellipse - : ImageRoundRadius::Small; if (auto prepared = PreparePhotoPreview( parent(), media, @@ -1103,13 +1119,17 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { const auto existing = options.existing; const auto spoilered = _spoiler || (_document->isVideoMessage() && ttlSeconds()); - if (auto found = FindCachedPreview(existing, _document, spoilered)) { + const auto radius = _document->isVideoMessage() + ? ImageRoundRadius::Ellipse + : ImageRoundRadius::Small; + if (auto found = FindCachedPreview( + existing, + _document, + radius, + spoilered)) { images.push_back(std::move(found)); } else if (TryFilePreview(_document)) { const auto media = _document->createMediaView(); - const auto radius = _document->isVideoMessage() - ? ImageRoundRadius::Ellipse - : ImageRoundRadius::Small; if (auto prepared = PrepareFilePreview( parent(), media, @@ -1811,13 +1831,17 @@ ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const { || _page->type == WebPageType::Video || _page->type == WebPageType::Document; if (pageTypeWithPreview || !_page->collage.items.empty()) { - if (auto found = FindCachedPreview(options.existing, _page, false)) { + const auto radius = ImageRoundRadius::Small; + if (auto found = FindCachedPreview( + options.existing, + _page, + radius, + false)) { return { .text = caption, .images = { std::move(found) } }; } auto context = std::any(); auto images = std::vector(); auto prepared = ItemPreviewImage(); - const auto radius = ImageRoundRadius::Small; if (const auto photo = MediaWebPage::photo()) { const auto media = photo->createMediaView(); prepared = PreparePhotoPreview(parent(), media, radius, false); @@ -2065,10 +2089,18 @@ ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const { if (!photo && !document) { continue; } else if (images.size() < kMaxPreviewImages) { - auto found = photo - ? FindCachedPreview(existing, not_null(photo), spoiler) - : FindCachedPreview(existing, not_null(document), spoiler); const auto radius = ImageRoundRadius::Small; + auto found = photo + ? FindCachedPreview( + existing, + not_null(photo), + radius, + spoiler) + : FindCachedPreview( + existing, + not_null(document), + radius, + spoiler); if (found) { images.push_back(std::move(found)); } else if (photo) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index 51fb657cdb..cbbe38518e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -370,7 +370,18 @@ void MessageView::paint( if (image.hasSpoiler()) { const auto frame = DefaultImageSpoiler().frame( _spoiler->index(context.now, pausedSpoiler)); - FillSpoilerRect(p, mini, frame); + if (image.isEllipse()) { + const auto radius = st::dialogsMiniPreview / 2; + static auto mask = Images::CornersMask(radius); + FillSpoilerRect( + p, + mini, + Images::CornersMaskRef(mask), + frame, + _cornersCache); + } else { + FillSpoilerRect(p, mini, frame); + } } } rect.setLeft(rect.x() + w); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 56aa8c2a9d..9fa6571c90 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -95,6 +95,7 @@ private: mutable std::unique_ptr _spoiler; mutable std::unique_ptr _loadingContext; mutable const style::DialogsMiniIcon *_leftIcon = nullptr; + mutable QImage _cornersCache; mutable bool _hasPlainLinkAtBegin = false; }; diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h index b29da9442d..9656d701ff 100644 --- a/Telegram/SourceFiles/history/view/history_view_item_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h @@ -16,6 +16,9 @@ struct ItemPreviewImage { [[nodiscard]] bool hasSpoiler() const { return (cacheKey & 1); } + [[nodiscard]] bool isEllipse() const { + return (cacheKey & 2); + } explicit operator bool() const { return !data.isNull(); From 46e7b6d6df82b76815f7a3b230e2b10da9824de7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Mar 2025 18:25:50 +0400 Subject: [PATCH 053/122] Implement "Choose gift to unpin" box feature. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/api/api_premium.cpp | 3 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 2 +- .../view/media/history_view_premium_gift.cpp | 3 +- .../peer_gifts/info_peer_gifts_common.cpp | 283 +++++++++++++++--- .../info/peer_gifts/info_peer_gifts_common.h | 32 +- .../peer_gifts/info_peer_gifts_widget.cpp | 68 +++-- .../settings/settings_credits_graphics.cpp | 116 +++++-- .../settings/settings_credits_graphics.h | 7 +- Telegram/SourceFiles/ui/effects/credits.style | 8 + 10 files changed, 419 insertions(+), 107 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5d4370b535..af2a2d6abd 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3429,7 +3429,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts."; "lng_gift_display_done_hide" = "The gift is now hidden from your profile page."; "lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts."; +"lng_gift_pinned_done_title" = "{gift} pinned"; "lng_gift_pinned_done" = "The gift will always be shown on top."; +"lng_gift_pinned_done_replaced" = "replacing {gift}"; "lng_gift_got_stars#one" = "You got **{count} Star** for this gift."; "lng_gift_got_stars#other" = "You got **{count} Stars** for this gift."; "lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift."; @@ -3502,6 +3504,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles."; "lng_gift_wear_start_toast" = "You put on {name}"; "lng_gift_wear_end_toast" = "You took off {name}"; +"lng_gift_many_pinned_title" = "Too Many Pinned Gifts"; +"lng_gift_many_pinned_choose" = "Select a gift to unpin below"; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 61769069fa..be3ead5afc 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -883,6 +883,7 @@ std::optional FromTL( unique->exportAt = data.vcan_export_at().value_or_empty(); } using Id = Data::SavedStarGiftId; + const auto hasUnique = parsed->unique != nullptr; return Data::SavedStarGift{ .info = std::move(*parsed), .manageId = (to->isUser() @@ -905,7 +906,7 @@ std::optional FromTL( .date = data.vdate().v, .upgradable = data.is_can_upgrade(), .anonymous = data.is_name_hidden(), - .pinned = data.is_pinned_to_top(), + .pinned = data.is_pinned_to_top() && hasUnique, .hidden = data.is_unsaved(), .mine = to->isSelf(), }; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index cb3fdf2520..59abb78468 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -1725,7 +1725,7 @@ void SendGiftBox( bool sending = false; }; const auto state = raw->lifetime().make_state(State{ - .delegate = Delegate(window, GiftButtonMode::Full), + .delegate = Delegate(&window->session(), GiftButtonMode::Full), }); const auto single = state->delegate.buttonSize(); const auto shadow = st::defaultDropdownMenu.wrap.shadow; diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index 700d6a1237..f9a9b031ca 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -479,7 +479,8 @@ ClickHandlerPtr OpenStarGiftLink(not_null item) { Settings::SavedStarGiftBox, window, owner, - *parsed)); + *parsed, + nullptr)); } } }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 803e7c4330..4118649948 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -7,20 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/peer_gifts/info_peer_gifts_common.h" +#include "boxes/send_credits_box.h" // SetButtonMarkedLabel #include "boxes/star_gift_box.h" #include "boxes/sticker_set_box.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_lottie.h" #include "core/ui_integration.h" #include "data/stickers/data_custom_emoji.h" +#include "data/data_credits.h" // CreditsHistoryEntry #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" #include "history/view/media/history_view_sticker_player.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "settings/settings_credits_graphics.h" +#include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" #include "ui/dynamic_image.h" #include "ui/dynamic_thumbnails.h" #include "ui/effects/premium_graphics.h" @@ -240,6 +245,76 @@ void GiftButton::setGeometry(QRect inner, QMargins extend) { AbstractButton::setGeometry(inner.marginsAdded(extend)); } +QMargins GiftButton::currentExtend() const { + const auto progress = _selectedAnimation.value(_selected ? 1. : 0.); + const auto added = anim::interpolate(0, st::giftBoxSelectSkip, progress); + return _extend + QMargins(added, added, added, added); +} + +void GiftButton::toggleSelected(bool selected) { + if (_selected == selected) { + return; + } + const auto duration = st::defaultRoundCheckbox.duration; + _selected = selected; + _selectedAnimation.start([=] { + update(); + }, selected ? 0. : 1., selected ? 1. : 0., duration, anim::easeOutCirc); +} + +void GiftButton::paintBackground(QPainter &p, const QImage &background) { + const auto removed = currentExtend() - _extend; + const auto x = removed.left(); + const auto y = removed.top(); + const auto width = this->width() - x - removed.right(); + const auto height = this->height() - y - removed.bottom(); + const auto dpr = int(background.devicePixelRatio()); + const auto bwidth = background.width() / dpr; + const auto bheight = background.height() / dpr; + const auto fillRow = [&](int yfrom, int ytill, int bfrom) { + const auto fill = [&](int xto, int wto, int xfrom, int wfrom = 0) { + const auto fheight = ytill - yfrom; + p.drawImage( + QRect(x + xto, y + yfrom, wto, fheight), + background, + QRect( + QPoint(xfrom, bfrom) * dpr, + QSize((wfrom ? wfrom : wto), fheight) * dpr)); + }; + if (width < bwidth) { + const auto xhalf = width / 2; + fill(0, xhalf, 0); + fill(xhalf, width - xhalf, bwidth - (width - xhalf)); + } else if (width == bwidth) { + fill(0, width, 0); + } else { + const auto half = bwidth / (2 * dpr); + fill(0, half, 0); + fill(width - half, half, bwidth - half); + fill(half, width - 2 * half, half, 1); + } + }; + if (height < bheight) { + fillRow(0, height / 2, 0); + fillRow(height / 2, height, bheight - (height - (height / 2))); + } else { + fillRow(0, height, 0); + } + + auto hq = PainterHighQualityEnabler(p); + const auto progress = _selectedAnimation.value(_selected ? 1. : 0.); + const auto pwidth = progress * st::defaultRoundCheckbox.width; + p.setPen(QPen(st::defaultRoundCheckbox.bgActive->c, pwidth)); + p.setBrush(Qt::NoBrush); + const auto rounded = rect().marginsRemoved(_extend); + const auto phalf = pwidth / 2.; + const auto extended = QRectF(rounded).marginsRemoved( + { phalf, phalf, phalf, phalf }); + const auto xradius = removed.left() + st::giftBoxGiftRadius - phalf; + const auto yradius = removed.top() + st::giftBoxGiftRadius - phalf; + p.drawRoundedRect(extended, xradius, yradius); +} + void GiftButton::resizeEvent(QResizeEvent *e) { if (!_button.isEmpty()) { _button.moveLeft((width() - _button.width()) / 2); @@ -267,9 +342,10 @@ void GiftButton::cacheUniqueBackground( [[maybe_unused]] const auto preload = _uniquePatternEmoji->ready(); } const auto outer = QRect(0, 0, width, height); + const auto extend = currentExtend(); const auto inner = outer.marginsRemoved( - _extend - ).translated(-_extend.left(), -_extend.top()); + extend + ).translated(-extend.left(), -extend.top()); const auto ratio = style::DevicePixelRatio(); if (_uniqueBackgroundCache.size() != inner.size() * ratio) { _uniqueBackgroundCache = QImage( @@ -313,32 +389,15 @@ void GiftButton::paintEvent(QPaintEvent *e) { : nullptr; const auto hidden = v::is(_descriptor) && v::get(_descriptor).hidden;; - const auto position = QPoint(_extend.left(), _extend.top()); + const auto extend = currentExtend(); + const auto position = QPoint(extend.left(), extend.top()); const auto background = _delegate->background(); - const auto dpr = int(background.devicePixelRatio()); const auto width = this->width(); - if (width * dpr <= background.width()) { - p.drawImage(0, 0, background); - } else { - const auto full = background.width(); - const auto half = ((full / 2) / dpr) * dpr; - const auto height = background.height(); - p.drawImage( - QRect(0, 0, half / dpr, height / dpr), - background, - QRect(0, 0, half, height)); - p.drawImage( - QRect(width - (half / dpr), 0, half / dpr, height / dpr), - background, - QRect(full - half, 0, half, height)); - p.drawImage( - QRect(half / dpr, 0, width - 2 * (half / dpr), height / dpr), - background, - QRect(half, 0, 1, height)); - } + const auto dpr = int(background.devicePixelRatio()); + paintBackground(p, background); if (unique) { cacheUniqueBackground(unique, width, background.height() / dpr); - p.drawImage(_extend.left(), _extend.top(), _uniqueBackgroundCache); + p.drawImage(extend.left(), extend.top(), _uniqueBackgroundCache); } if (_userpic) { @@ -348,7 +407,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { } const auto image = _userpic->image(st::giftBoxUserpicSize); const auto skip = st::giftBoxUserpicSkip; - p.drawImage(_extend.left() + skip, _extend.top() + skip, image); + p.drawImage(extend.left() + skip, extend.top() + skip, image); } auto frame = QImage(); @@ -401,7 +460,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { auto hq = PainterHighQualityEnabler(p); const auto premium = v::is(_descriptor); - const auto singlew = width - _extend.left() - _extend.right(); + const auto singlew = width - extend.left() - extend.right(); const auto font = st::semiboldFont; p.setFont(font); @@ -420,11 +479,17 @@ void GiftButton::paintEvent(QPaintEvent *e) { } return GiftBadge(); }, [&](const GiftTypeStars &data) { - if (const auto count = data.info.limitedCount) { - const auto soldOut = !data.userpic && !data.info.limitedLeft; + const auto count = data.info.limitedCount; + const auto pinned = data.pinned || data.pinnedSelection; + if (count || pinned) { + const auto soldOut = !pinned + && !data.userpic + && !data.info.limitedLeft; return GiftBadge{ .text = (soldOut ? tr::lng_gift_stars_sold_out(tr::now) + : (unique && pinned) + ? ('#' + QString::number(unique->number)) : (!data.userpic && !data.info.unique) ? tr::lng_gift_stars_limited(tr::now) : (count == 1) @@ -452,7 +517,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { if (badge) { const auto rubberOut = st::lineWidth; - const auto inner = rect().marginsRemoved(_extend); + const auto inner = rect().marginsRemoved(extend); p.setClipRect(inner.marginsAdded( { rubberOut, rubberOut, rubberOut, rubberOut })); @@ -474,7 +539,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { p.setPen(Qt::NoPen); p.setBrush(unique->backdrop.patternColor); const auto rect = QRect( - QPoint(_extend.left() + skip, _extend.top() + skip), + QPoint(extend.left() + skip, extend.top() + skip), QSize(icon.width() + 2 * add, icon.height() + 2 * add)); p.drawEllipse(rect); icon.paintInCenter(p, rect); @@ -547,12 +612,10 @@ void GiftButton::paintEvent(QPaintEvent *e) { } } -Delegate::Delegate( - not_null window, - GiftButtonMode mode) -: _window(window) +Delegate::Delegate(not_null session, GiftButtonMode mode) +: _session(session) , _hiddenMark(std::make_unique( - &window->session(), + _session, st::giftBoxHiddenMark, RectPart::Center)) , _mode(mode) { @@ -563,18 +626,17 @@ Delegate::Delegate(Delegate &&other) = default; Delegate::~Delegate() = default; TextWithEntities Delegate::star() { - const auto owner = &_window->session().data(); - return owner->customEmojiManager().creditsEmoji(); + return _session->data().customEmojiManager().creditsEmoji(); } TextWithEntities Delegate::ministar() { - const auto owner = &_window->session().data(); + const auto owner = &_session->data(); const auto top = st::giftBoxByStarsStarTop; return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 }); } Ui::Text::MarkedContext Delegate::textContext() { - return Core::TextContext({ .session = &_window->session() }); + return Core::TextContext({ .session = _session }); } QSize Delegate::buttonSize() { @@ -601,7 +663,7 @@ auto Delegate::buttonPatternEmoji( not_null unique, Fn repaint) -> std::unique_ptr { - return _window->session().data().customEmojiManager().create( + return _session->data().customEmojiManager().create( unique->pattern.document, repaint, Data::CustomEmojiSizeTag::Large); @@ -658,7 +720,7 @@ QImage Delegate::background() { rpl::producer> Delegate::sticker( const GiftDescriptor &descriptor) { - return GiftStickerValue(&_window->session(), descriptor); + return GiftStickerValue(_session, descriptor); } not_null Delegate::hiddenMark() { @@ -784,4 +846,143 @@ QImage ValidateRotatedBadge(const GiftBadge &badge, int added) { return result; } +void SelectGiftToUnpin( + std::shared_ptr show, + const std::vector &pinned, + Fn chosen) { + show->show(Box([=](not_null box) { + struct State { + explicit State(not_null session) + : delegate(session, GiftButtonMode::Minimal) { + } + + Delegate delegate; + rpl::variable selected = -1; + std::vector> buttons; + }; + const auto session = &show->session(); + const auto state = box->lifetime().make_state(session); + + box->setStyle(st::giftTooManyPinnedBox); + box->setWidth(st::boxWideWidth); + + box->addRow( + object_ptr( + box, + tr::lng_gift_many_pinned_title(), + st::giftBoxSubtitle), + st::giftBoxSubtitleMargin); + box->addRow( + object_ptr( + box, + tr::lng_gift_many_pinned_choose(), + st::giftTooManyPinnedChoose), + st::giftBoxAboutMargin); + + const auto gifts = box->addRow( + object_ptr(box), + QMargins( + st::giftBoxPadding.left(), + st::giftTooManyPinnedBox.buttonPadding.top(), + st::giftBoxPadding.right(), + 0)); + for (const auto &entry : pinned) { + const auto index = int(state->buttons.size()); + state->buttons.push_back( + Ui::CreateChild(gifts, &state->delegate)); + const auto button = state->buttons.back(); + button->setDescriptor(GiftTypeStars{ + .info = { + .id = entry.stargiftId, + .unique = entry.uniqueGift, + .document = entry.uniqueGift->model.document, + }, + .pinnedSelection = true, + }, GiftButton::Mode::Minimal); + button->setClickedCallback([=] { + const auto now = state->selected.current(); + state->selected = (now == index) ? -1 : index; + }); + } + + state->selected.value( + ) | rpl::combine_previous( + ) | rpl::start_with_next([=](int old, int now) { + if (old >= 0) state->buttons[old]->toggleSelected(false); + if (now >= 0) state->buttons[now]->toggleSelected(true); + }, gifts->lifetime()); + + gifts->widthValue() | rpl::start_with_next([=](int width) { + const auto singleMin = state->delegate.buttonSize(); + if (width < singleMin.width()) { + return; + } + const auto count = int(state->buttons.size()); + const auto skipw = st::giftBoxGiftSkip.x(); + const auto skiph = st::giftBoxGiftSkip.y(); + const auto perRow = std::min( + (width + skipw) / (singleMin.width() + skipw), + std::max(count, 1)); + if (perRow <= 0) { + return; + } + const auto single = (width - (perRow - 1) * skipw) / perRow; + const auto height = singleMin.height(); + const auto rows = (count + perRow - 1) / perRow; + for (auto row = 0; row != rows; ++row) { + const auto y = row * (height + skiph); + for (auto column = 0; column != perRow; ++column) { + const auto index = row * perRow + column; + if (index >= count) { + break; + } + const auto &button = state->buttons[index]; + const auto x = column * (single + skipw); + button->setGeometry( + QRect(x, y, single, height), + state->delegate.buttonExtend()); + } + } + gifts->resize(width, rows * (height + skiph) - skiph); + }, gifts->lifetime()); + + const auto button = box->addButton(rpl::single(QString()), [=] { + const auto index = state->selected.current(); + if (index < 0) { + return; + } + Assert(index < int(pinned.size())); + const auto &entry = pinned[index]; + const auto weak = Ui::MakeWeak(box); + chosen(::Settings::EntryToSavedStarGiftId(session, entry)); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }); + const auto label = Ui::SetButtonMarkedLabel( + button, + tr::lng_context_unpin_from_top(Ui::Text::WithEntities), + &show->session(), + st::creditsBoxButtonLabel, + &st::giftTooManyPinnedBox.button.textFg); + + state->selected.value() | rpl::start_with_next([=](int value) { + const auto has = (value >= 0); + label->setOpacity(has ? 1. : 0.5); + button->setAttribute(Qt::WA_TransparentForMouseEvents, !has); + }, box->lifetime()); + + const auto buttonPadding = st::giftTooManyPinnedBox.buttonPadding; + const auto buttonWidth = st::boxWideWidth + - buttonPadding.left() + - buttonPadding.right(); + button->resizeToWidth(buttonWidth); + button->widthValue() | rpl::start_with_next([=](int width) { + if (width != buttonWidth) { + button->resizeToWidth(buttonWidth); + } + }, button->lifetime()); + })); +} + } // namespace Info::PeerGifts diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 0d93a8bf21..2c0ae5ef1c 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -15,8 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class StickerPremiumMark; +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { struct UniqueGift; +struct CreditsHistoryEntry; +class SavedStarGiftId; } // namespace Data namespace HistoryView { @@ -57,10 +63,11 @@ struct GiftTypeStars { Data::StarGift info; PeerData *from = nullptr; TimeId date = 0; - bool userpic = false; - bool pinned = false; - bool hidden = false; - bool mine = false; + bool pinnedSelection : 1 = false; + bool userpic : 1 = false; + bool pinned : 1 = false; + bool hidden : 1 = false; + bool mine : 1 = false; [[nodiscard]] friend inline bool operator==( const GiftTypeStars&, @@ -128,6 +135,8 @@ public: void setDescriptor(const GiftDescriptor &descriptor, Mode mode); void setGeometry(QRect inner, QMargins extend); + void toggleSelected(bool selected); + [[nodiscard]] rpl::producer contextMenuRequests() const { return _contextMenuRequests.events(); } @@ -137,6 +146,7 @@ private: void resizeEvent(QResizeEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; + void paintBackground(QPainter &p, const QImage &background); void cacheUniqueBackground( not_null unique, int width, @@ -144,6 +154,7 @@ private: void setDocument(not_null document); [[nodiscard]] bool documentResolved() const; + [[nodiscard]] QMargins currentExtend() const; void unsubscribe(); @@ -159,8 +170,10 @@ private: std::unique_ptr _uniquePatternEmoji; base::flat_map _uniquePatternCache; std::optional _stars; + Ui::Animations::Simple _selectedAnimation; bool _subscribed = false; bool _patterned = false; + bool _selected = false; bool _small = false; QRect _button; @@ -173,9 +186,7 @@ private: class Delegate final : public GiftButtonDelegate { public: - Delegate( - not_null window, - GiftButtonMode mode); + Delegate(not_null session, GiftButtonMode mode); Delegate(Delegate &&other); ~Delegate(); @@ -195,7 +206,7 @@ public: QImage cachedBadge(const GiftBadge &badge) override; private: - const not_null _window; + const not_null _session; std::unique_ptr _hiddenMark; base::flat_map _badges; QSize _single; @@ -214,4 +225,9 @@ private: [[nodiscard]] QImage ValidateRotatedBadge(const GiftBadge &badge, int added); +void SelectGiftToUnpin( + std::shared_ptr show, + const std::vector &pinned, + Fn chosen); + } // namespace Info::PeerGifts diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 683501acf6..88745c6b92 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -112,6 +112,9 @@ private: int resizeGetHeight(int width) override; + [[nodiscard]] auto pinnedSavedGifts() + -> Fn()>; + const not_null _window; rpl::variable _filter; Delegate _delegate; @@ -152,7 +155,7 @@ InnerWidget::InnerWidget( : BoxContentDivider(parent) , _window(controller->parentController()) , _filter(std::move(filter)) -, _delegate(_window, GiftButtonMode::Minimal) +, _delegate(&_window->session(), GiftButtonMode::Minimal) , _controller(controller) , _peer(peer) , _totalCount(_peer->peerGiftsCount()) @@ -225,6 +228,9 @@ void InnerWidget::subscribeToUpdates() { return; } refreshButtons(); + if (update.action == Action::Pin) { + _scrollToTop.fire({}); + } }, lifetime()); } @@ -476,6 +482,41 @@ void InnerWidget::validateButtons() { std::swap(_views, views); } +auto InnerWidget::pinnedSavedGifts() +-> Fn()> { + struct Entry { + Data::SavedStarGiftId id; + std::shared_ptr unique; + }; + auto entries = std::vector(); + for (const auto &entry : _entries) { + if (entry.gift.pinned) { + Assert(entry.gift.info.unique != nullptr); + entries.push_back({ + entry.gift.manageId, + entry.gift.info.unique, + }); + } else { + break; + } + } + return [entries] { + auto result = std::vector(); + result.reserve(entries.size()); + for (const auto &entry : entries) { + const auto &id = entry.id; + result.push_back({ + .bareMsgId = uint64(id.userMessageId().bare), + .bareEntryOwnerId = id.chat() ? id.chat()->id.value : 0, + .giftChannelSavedId = id.chatSavedId(), + .uniqueGift = entry.unique, + .stargift = true, + }); + } + return result; + }; +} + void InnerWidget::showMenuFor(not_null button, QPoint point) { if (_menu) { return; @@ -495,27 +536,7 @@ void InnerWidget::showMenuFor(not_null button, QPoint point) { auto entry = ::Settings::SavedStarGiftEntry( _peer, _entries[index].gift); - auto pinnedIds = std::vector(); - for (const auto &entry : _entries) { - if (entry.gift.pinned) { - pinnedIds.push_back(entry.gift.manageId); - } else { - break; - } - } - entry.pinnedSavedGifts = [pinnedIds] { - auto result = std::vector(); - result.reserve(pinnedIds.size()); - for (const auto &id : pinnedIds) { - result.push_back({ - .bareMsgId = uint64(id.userMessageId().bare), - .bareEntryOwnerId = id.chat() ? id.chat()->id.value : 0, - .giftChannelSavedId = id.chatSavedId(), - .stargift = true, - }); - } - return result; - }; + entry.pinnedSavedGifts = pinnedSavedGifts(); _menu = base::make_unique_q(this, st::popupMenuWithIcons); ::Settings::FillSavedStarGiftMenu( _controller->uiShow(), @@ -535,7 +556,8 @@ void InnerWidget::showGift(int index) { ::Settings::SavedStarGiftBox, _window, _peer, - _entries[index].gift)); + _entries[index].gift, + pinnedSavedGifts())); } void InnerWidget::refreshAbout() { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 4056c3bfb6..9aee9306f5 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/bot/starref/info_bot_starref_common.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. #include "info/channel_statistics/earn/info_channel_earn_widget.h" // Info::ChannelEarn::Make. +#include "info/peer_gifts/info_peer_gifts_common.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "info/statistics/info_statistics_list_controllers.h" #include "info/info_controller.h" @@ -173,18 +174,6 @@ private: }; -[[nodiscard]] Data::SavedStarGiftId EntryToSavedStarGiftId( - not_null session, - const Data::CreditsHistoryEntry &entry) { - return !entry.stargift - ? Data::SavedStarGiftId() - : (entry.bareEntryOwnerId && entry.giftChannelSavedId) - ? Data::SavedStarGiftId::Chat( - session->data().peer(PeerId(entry.bareEntryOwnerId)), - entry.giftChannelSavedId) - : Data::SavedStarGiftId::User(MsgId(entry.bareMsgId)); -} - void ToggleStarGiftSaved( std::shared_ptr show, Data::SavedStarGiftId savedId, @@ -226,7 +215,8 @@ void ToggleStarGiftPinned( Data::SavedStarGiftId savedId, std::vector already, bool pinned, - Fn done = nullptr) { + std::shared_ptr uniqueData = nullptr, + std::shared_ptr replacingData = nullptr) { already.erase(ranges::remove(already, savedId), end(already)); if (pinned) { already.insert(begin(already), savedId); @@ -256,16 +246,29 @@ void ToggleStarGiftPinned( .action = (pinned ? GiftAction::Pin : GiftAction::Unpin), }); - if (const auto onstack = done) { - onstack(true); - } if (pinned) { - show->showToast(tr::lng_gift_pinned_done(tr::now)); + show->showToast({ + .title = (uniqueData + ? tr::lng_gift_pinned_done_title( + tr::now, + lt_gift, + Data::UniqueGiftName(*uniqueData)) + : QString()), + .text = (replacingData + ? tr::lng_gift_pinned_done_replaced( + tr::now, + lt_gift, + TextWithEntities{ + Data::UniqueGiftName(*replacingData), + }, + Ui::Text::WithEntities) + : tr::lng_gift_pinned_done( + tr::now, + Ui::Text::WithEntities)), + .duration = Ui::Toast::kDefaultDuration * 2, + }); } }).fail([=](const MTP::Error &error) { - if (const auto onstack = done) { - onstack(false); - } show->showToast(error.type()); }).send(); } @@ -920,25 +923,61 @@ void FillUniqueGiftMenu( if (unique && canToggle && e.savedToProfile - && type == SavedStarGiftMenuType::List) { - const auto already = [session, entries = e.pinnedSavedGifts] { - Expects(entries != nullptr); - - auto list = entries(); + && e.pinnedSavedGifts) { + const auto pinned = e.pinnedSavedGifts; + const auto ids = [session]( + const std::vector &pinned) { auto result = std::vector(); - result.reserve(list.size()); - for (const auto &entry : list) { + result.reserve(pinned.size()); + for (const auto &entry : pinned) { result.push_back(EntryToSavedStarGiftId(session, entry)); } return result; }; if (e.giftPinned) { menu->addAction(tr::lng_context_unpin_from_top(tr::now), [=] { - ToggleStarGiftPinned(show, savedId, already(), false); + ToggleStarGiftPinned(show, savedId, ids(pinned()), false); }, st.unpin ? st.unpin : &st::menuIconUnpin); } else { menu->addAction(tr::lng_context_pin_to_top(tr::now), [=] { - ToggleStarGiftPinned(show, savedId, already(), true); + const auto list = pinned(); + const auto &appConfig = show->session().appConfig(); + const auto limit = appConfig.pinnedGiftsLimit(); + auto already = ids(list); + if (list.size() >= limit) { + Info::PeerGifts::SelectGiftToUnpin(show, list, [=]( + Data::SavedStarGiftId id) { + auto copy = already; + const auto i = ranges::find(copy, id); + const auto replaced = (i != end(copy)) + ? list[i - begin(copy)].uniqueGift + : nullptr; + if (i != end(copy)) { + copy.erase(i); + } + + using GiftAction = Data::GiftUpdate::Action; + show->session().data().notifyGiftUpdate({ + .id = id, + .action = GiftAction::Unpin, + }); + + ToggleStarGiftPinned( + show, + savedId, + already, + true, + unique, + replaced); + }); + } else { + ToggleStarGiftPinned( + show, + savedId, + already, + true, + unique); + } }, st.pin ? st.pin : &st::menuIconPin); } } @@ -2031,15 +2070,30 @@ Data::CreditsHistoryEntry SavedStarGiftEntry( }; } +Data::SavedStarGiftId EntryToSavedStarGiftId( + not_null session, + const Data::CreditsHistoryEntry &entry) { + return !entry.stargift + ? Data::SavedStarGiftId() + : (entry.bareEntryOwnerId && entry.giftChannelSavedId) + ? Data::SavedStarGiftId::Chat( + session->data().peer(PeerId(entry.bareEntryOwnerId)), + entry.giftChannelSavedId) + : Data::SavedStarGiftId::User(MsgId(entry.bareMsgId)); +} + void SavedStarGiftBox( not_null box, not_null controller, not_null owner, - const Data::SavedStarGift &data) { + const Data::SavedStarGift &data, + Fn()> pinned) { + auto entry = SavedStarGiftEntry(owner, data); + entry.pinnedSavedGifts = std::move(pinned); Settings::ReceiptCreditsBox( box, controller, - SavedStarGiftEntry(owner, data), + std::move(entry), Data::SubscriptionEntry()); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 8a1ee55bcd..aa43eff741 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -24,6 +24,7 @@ struct SubscriptionEntry; struct GiftCode; struct CreditTopupOption; struct SavedStarGift; +class SavedStarGiftId; struct StarGift; } // namespace Data @@ -158,11 +159,15 @@ void GlobalStarGiftBox( [[nodiscard]] Data::CreditsHistoryEntry SavedStarGiftEntry( not_null owner, const Data::SavedStarGift &data); +[[nodiscard]] Data::SavedStarGiftId EntryToSavedStarGiftId( + not_null session, + const Data::CreditsHistoryEntry &entry); void SavedStarGiftBox( not_null box, not_null controller, not_null owner, - const Data::SavedStarGift &data); + const Data::SavedStarGift &data, + Fn()> pinned = nullptr); enum class SavedStarGiftMenuType { List, View, diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 906f29c7da..ae44f06329 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -149,6 +149,7 @@ giftBoxStickerStarTop: 24px; giftBoxSmallStickerTop: 16px; giftBoxStickerTopByStars: -4px; giftBoxStickerSize: size(80px, 80px); +giftBoxSelectSkip: 5px; giftBoxUserpicSize: 24px; giftBoxUserpicSkip: 2px; giftBoxTextField: InputField(defaultInputField) { @@ -256,3 +257,10 @@ darkUpgradeGiftInfoTitle: FlatLabel(defaultFlatLabel) { darkUpgradeGiftInfoAbout: FlatLabel(upgradeGiftSubtext) { textFg: groupCallMemberNotJoinedStatus; } + +giftTooManyPinnedBox: Box(giftBox) { + buttonPadding: margins(11px, 11px, 11px, 11px); +} +giftTooManyPinnedChoose: FlatLabel(giftBoxAbout) { + textFg: windowSubTextFg; +} From c924fcb91f5ca1dddbb03a25c035c477f9ec5c1c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 17:21:50 +0400 Subject: [PATCH 054/122] Fix custom emoji in poll results view. --- .../info/polls/info_polls_results_inner_widget.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp index 18fdcce09f..24afb84377 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/polls/info_polls_results_widget.h" #include "lang/lang_keys.h" +#include "core/ui_integration.h" #include "data/data_peer.h" #include "data/data_poll.h" #include "data/data_session.h" @@ -461,7 +462,9 @@ ListController *CreateAnswerRows( .append(QString::fromUtf8(" \xe2\x80\x94 ")) .append(QString::number(percent)) .append('%')), - st::boxDividerLabel), + st::boxDividerLabel, + st::defaultPopupMenu, + Core::TextContext({ .session = session })), style::margins( st::pollResultsHeaderPadding.left(), st::pollResultsHeaderPadding.top(), @@ -610,7 +613,9 @@ void InnerWidget::setupContent() { object_ptr( _content, rpl::single(_poll->question), - st::pollResultsQuestion), + st::pollResultsQuestion, + st::defaultPopupMenu, + Core::TextContext({ .session = &_controller->session() })), st::boxRowPadding); Ui::AddSkip(_content, st::boxLittleSkip / 2); _content->add( From c30a5782df11e37a9834a78d4fafa647b99e41c0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 17:26:44 +0400 Subject: [PATCH 055/122] Revert "Fixed blinking of message preview from user's personal channel." This reverts commit 75e454f3fd69be3e2e4cb54ca42daf5b14ca91d1. --- .../dialogs/ui/dialogs_message_view.cpp | 11 ++++------- .../SourceFiles/dialogs/ui/dialogs_message_view.h | 3 +-- .../info/profile/info_profile_actions.cpp | 14 +++----------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index cbbe38518e..b45708b844 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -150,8 +150,7 @@ void MessageView::prepare( not_null item, Data::Forum *forum, Fn customEmojiRepaint, - ToPreviewOptions options, - Fn customLoadingFinishCallback) { + ToPreviewOptions options) { if (!forum) { _topics = nullptr; } else if (!_topics || _topics->forum() != forum) { @@ -213,11 +212,9 @@ void MessageView::prepare( if (!_loadingContext) { _loadingContext = std::make_unique(); item->history()->session().downloaderTaskFinished( - ) | rpl::start_with_next( - customLoadingFinishCallback - ? customLoadingFinishCallback - : Fn([=] { _textCachedFor = nullptr; }), - _loadingContext->lifetime); + ) | rpl::start_with_next([=] { + _textCachedFor = nullptr; + }, _loadingContext->lifetime); } _loadingContext->context = std::move(preview.loadingContext); } else { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 9fa6571c90..4ee12aebd3 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -61,8 +61,7 @@ public: not_null item, Data::Forum *forum, Fn customEmojiRepaint, - ToPreviewOptions options, - Fn customLoadingFinishCallback = nullptr); + ToPreviewOptions options); void paint( Painter &p, diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 5f1f66b780..b9a94d3bdf 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -1698,18 +1698,10 @@ object_ptr DetailsFiller::setupPersonalChannel( auto &lifetime = preview->lifetime(); using namespace Dialogs::Ui; const auto previewView = lifetime.make_state(); + const auto previewUpdate = [=] { preview->update(); }; preview->resize(0, st::infoLabeled.style.font->height); - const auto prepare = [previewView, preview]( - not_null item) { - previewView->prepare( - item, - nullptr, - [=] { preview->update(); }, - {}, - []{}); - }; if (!previewView->dependsOn(item)) { - prepare(item); + previewView->prepare(item, nullptr, previewUpdate, {}); } preview->paintRequest( ) | rpl::start_with_next([=, fullId = item->fullId()]( @@ -1739,7 +1731,7 @@ object_ptr DetailsFiller::setupPersonalChannel( preview->rect(), tr::lng_contacts_loading(tr::now), style::al_left); - prepare(item); + previewView->prepare(item, nullptr, previewUpdate, {}); preview->update(); } }, preview->lifetime()); From 96fdece4785c9d9d9c3a3cc3b2b767995824e697 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 17:42:33 +0400 Subject: [PATCH 056/122] Fix mini-thumbs in channel last message preview. --- .../info/profile/info_profile_actions.cpp | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index b9a94d3bdf..318260d5b1 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -1697,17 +1697,28 @@ object_ptr DetailsFiller::setupPersonalChannel( const auto preview = Ui::CreateChild(line); auto &lifetime = preview->lifetime(); using namespace Dialogs::Ui; - const auto previewView = lifetime.make_state(); - const auto previewUpdate = [=] { preview->update(); }; + struct State { + MessageView view; + HistoryItem *item = nullptr; + rpl::lifetime lifetime; + }; + const auto state = lifetime.make_state(); + state->item = item; + item->history()->session().changes().realtimeMessageUpdates( + Data::MessageUpdate::Flag::Destroyed + ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { + if (update.item == state->item) { + state->lifetime.destroy(); + state->item = nullptr; + preview->update(); + } + }, state->lifetime); + preview->resize(0, st::infoLabeled.style.font->height); - if (!previewView->dependsOn(item)) { - previewView->prepare(item, nullptr, previewUpdate, {}); - } preview->paintRequest( - ) | rpl::start_with_next([=, fullId = item->fullId()]( - const QRect &rect) { + ) | rpl::start_with_next([=] { auto p = Painter(preview); - const auto item = user->session().data().message(fullId); + const auto item = state->item; if (!item) { p.setPen(st::infoPersonalChannelDateLabel.textFg); p.setBrush(Qt::NoBrush); @@ -1718,22 +1729,14 @@ object_ptr DetailsFiller::setupPersonalChannel( style::al_left); return; } - if (previewView->prepared(item, nullptr)) { - previewView->paint(p, preview->rect(), { - .st = &st::defaultDialogRow, - .currentBg = st::boxBg->b, - }); - } else if (!previewView->dependsOn(item)) { - p.setPen(st::infoPersonalChannelDateLabel.textFg); - p.setBrush(Qt::NoBrush); - p.setFont(st::infoPersonalChannelDateLabel.style.font); - p.drawText( - preview->rect(), - tr::lng_contacts_loading(tr::now), - style::al_left); - previewView->prepare(item, nullptr, previewUpdate, {}); - preview->update(); + if (!state->view.prepared(item, nullptr)) { + const auto repaint = [=] { preview->update(); }; + state->view.prepare(item, nullptr, repaint, {}); } + state->view.paint(p, preview->rect(), { + .st = &st::defaultDialogRow, + .currentBg = st::boxBg->b, + }); }, preview->lifetime()); line->sizeValue() | rpl::filter_size( From f3ea24f9f9b0886e74c93fb09db803d6bd6ef9b3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Mar 2025 12:46:45 +0400 Subject: [PATCH 057/122] Update lib_ui. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 107729e446..9255267c81 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 107729e446e8a2037ceb68374f90978fec937504 +Subproject commit 9255267c813bdc7105e77d0643b5a77e6dc66ad4 From ad9106b815f76bdd14c37e4f88767145a8f53e71 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 16 Mar 2025 18:57:46 +0300 Subject: [PATCH 058/122] Added ability to request reach out ratio of swipe. --- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 9 +++++++++ Telegram/SourceFiles/ui/controls/swipe_handler.h | 1 + 2 files changed, 10 insertions(+) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index 3ec1f32ca6..c49b3d61b4 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -213,6 +213,7 @@ void SetupSwipeHandler( if (!state->started || state->touch != args.touch) { LOG(("STARTING")); state->started = true; + state->data.reachRatio = 0.; state->touch = args.touch; state->startAt = args.position; state->cursorTop = widget->mapFromGlobal(args.globalCursor).y(); @@ -265,6 +266,14 @@ void SetupSwipeHandler( base::Platform::Haptic(); } else if (state->reached && ratio < kResetReachedOn) { + if (state->finishByTopData.provideReachOutRatio) { + state->animationReach.stop(); + state->animationReach.start( + animationReachCallback, + 1., + 0., + kBounceDuration); + } state->reached = false; } } diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index 79413091eb..b46e8e3dfc 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -23,6 +23,7 @@ struct SwipeHandlerFinishData { int64 msgBareId = 0; float64 speedRatio = 1.0; bool keepRatioWithinRange = false; + bool provideReachOutRatio = false; }; using Scroll = std::variant< From c29d78ac0d0aaa07527db328a6bcc99fc99dda40 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 16 Mar 2025 19:00:59 +0300 Subject: [PATCH 059/122] Added ability to provide reach ratio speed to swipe. --- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 8 ++++++-- Telegram/SourceFiles/ui/controls/swipe_handler.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index c49b3d61b4..f5c3cb60ce 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -262,7 +262,9 @@ void SetupSwipeHandler( animationReachCallback, 0., 1., - kBounceDuration); + state->finishByTopData.reachRatioDuration + ? state->finishByTopData.reachRatioDuration + : kBounceDuration); base::Platform::Haptic(); } else if (state->reached && ratio < kResetReachedOn) { @@ -272,7 +274,9 @@ void SetupSwipeHandler( animationReachCallback, 1., 0., - kBounceDuration); + state->finishByTopData.reachRatioDuration + ? state->finishByTopData.reachRatioDuration + : kBounceDuration); } state->reached = false; } diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index b46e8e3dfc..6d3f796b1e 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -22,6 +22,7 @@ struct SwipeHandlerFinishData { Fn callback; int64 msgBareId = 0; float64 speedRatio = 1.0; + crl::time reachRatioDuration = 0; bool keepRatioWithinRange = false; bool provideReachOutRatio = false; }; From e28d29f276ad567b3d0ac13fc452406c7aeaa449 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 16 Mar 2025 12:11:49 +0300 Subject: [PATCH 060/122] Added initial implementation of swipe action to dialogs. --- Telegram/CMakeLists.txt | 2 + .../animations/swipe_action/archive.tgs | Bin 0 -> 1088 bytes .../animations/swipe_action/delete.tgs | Bin 0 -> 1248 bytes .../animations/swipe_action/disabled.tgs | Bin 0 -> 731 bytes .../animations/swipe_action/mute.tgs | Bin 0 -> 2586 bytes .../Resources/animations/swipe_action/pin.tgs | Bin 0 -> 1387 bytes .../animations/swipe_action/read.tgs | Bin 0 -> 1860 bytes .../animations/swipe_action/unarchive.tgs | Bin 0 -> 1191 bytes .../animations/swipe_action/unmute.tgs | Bin 0 -> 2186 bytes .../animations/swipe_action/unpin.tgs | Bin 0 -> 2607 bytes .../animations/swipe_action/unread.tgs | Bin 0 -> 2084 bytes Telegram/Resources/langs/lang.strings | 11 ++ .../Resources/qrc/telegram/animations.qrc | 11 ++ Telegram/SourceFiles/dialogs/dialogs.style | 2 + .../dialogs/dialogs_inner_widget.cpp | 72 +++++++++++ .../dialogs/dialogs_inner_widget.h | 15 +++ .../dialogs/dialogs_swipe_action.cpp | 29 +++++ .../dialogs/dialogs_swipe_action.h | 22 ++++ .../SourceFiles/dialogs/dialogs_widget.cpp | 60 +++++++-- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 117 +++++++++++++++++- .../SourceFiles/dialogs/ui/dialogs_layout.h | 5 +- .../dialogs/ui/dialogs_swipe_context.h | 35 ++++++ Telegram/cmake/td_ui.cmake | 1 + 23 files changed, 362 insertions(+), 20 deletions(-) create mode 100644 Telegram/Resources/animations/swipe_action/archive.tgs create mode 100644 Telegram/Resources/animations/swipe_action/delete.tgs create mode 100644 Telegram/Resources/animations/swipe_action/disabled.tgs create mode 100644 Telegram/Resources/animations/swipe_action/mute.tgs create mode 100644 Telegram/Resources/animations/swipe_action/pin.tgs create mode 100644 Telegram/Resources/animations/swipe_action/read.tgs create mode 100644 Telegram/Resources/animations/swipe_action/unarchive.tgs create mode 100644 Telegram/Resources/animations/swipe_action/unmute.tgs create mode 100644 Telegram/Resources/animations/swipe_action/unpin.tgs create mode 100644 Telegram/Resources/animations/swipe_action/unread.tgs create mode 100644 Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp create mode 100644 Telegram/SourceFiles/dialogs/dialogs_swipe_action.h create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 028979462b..24b7ed6722 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -688,6 +688,8 @@ PRIVATE dialogs/dialogs_search_from_controllers.h dialogs/dialogs_search_tags.cpp dialogs/dialogs_search_tags.h + dialogs/dialogs_swipe_action.cpp + dialogs/dialogs_swipe_action.h dialogs/dialogs_widget.cpp dialogs/dialogs_widget.h editor/color_picker.cpp diff --git a/Telegram/Resources/animations/swipe_action/archive.tgs b/Telegram/Resources/animations/swipe_action/archive.tgs new file mode 100644 index 0000000000000000000000000000000000000000..73e049e69c344c5da84187d48ff2dc1b55cf534c GIT binary patch literal 1088 zcmV-G1i$+qiwFp_wn1b717m1mbaP)}a${&|c4aPVb8l_{?O020BR3GeiorFT;HTLi z?L0|l4luxM3@tjcJksdXLaXhuVF(Da&K+`vNq```oI$?PR2A7Ds?~#FU!F5W{c(B9JH>l zuSvYv@8|WNO|GV#t&4|wh5g5tz1%{pIIADnB=%t-(BJ`-+5Cp!>*5 z{1l|Uf?kxjXrItdK|#i@X7*=TtmvT<7f=Y^638YxL) zzbJNd=O{YHK33q*vZev@SUQhEkohSZ@7gIq66CLF75*DZVH1v@{!p3^M4~`Oa+2~i zEVt|%?(^V#Mm?NixCU?a4y{H?a)axDRtp_tHy28ShSEqRBFlKBJr#=-rbC6U7fAGV zJCigCOgUqdn_|76AG$D72K`WO>+%*ieX|Af^2awnU3{3&>ax0ckDwaHX3Abrqoa5LghR5HZ zemVYi{O9-wv|o;YL+{Vym#1H!eq)Deh!oo$ZS#!0Yn8-8^NJ*p=2JT%WQX9KP8*4feg-s_6w9bh( zWE!svN4u$7SBQ4^L0#R#fo`(ezJ|OPa?|XikGS#s=mXx7xg$-V{9lKA`kV#8&AL|r z41flF$_#`VfyMWGpo4)+(w2*T2R_EQRFoo??#Cj?0zm3Cwn}9xRzPNH7Q-onEzT{} z8$ORs+$blRHdg0}R4AQF1>H<)nPy088|P9h9ml9U?e5x<%gDOBNQG6%+vR%w?Ha^O zyjCFX)&HNDAZGApQqNm~G*>wl1onHLO6QJ_h6CJullGvv;`-zmrQCl^cid7o>%H1MFo-#XL_%a)p<0CV)a1YO?OjuyF9mpei zdHG)Ad5#seot70f;ysLgePIo*hO;YwMcbEs?YO literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/delete.tgs b/Telegram/Resources/animations/swipe_action/delete.tgs new file mode 100644 index 0000000000000000000000000000000000000000..daa3a01626fc0d4c084ebb6a878d68c3270a0a05 GIT binary patch literal 1248 zcmV<61Rwh!iwFp_wn1b719Nw2aAjX)Wo%`1WiD!SZ*BnXSW9o?HW2nO2n*)k+0+eP62-r-9oB{^|k1p+J*i6e42!}&Nv&iJ0(kvS2e z2vb7IuIvgjzb7}?_gmd{oATF9RkpJapJ!1BA&Za)83;C8-CXIWTgmoF$#p~KNkqx! zK<3!g*zk;!?_@53QL;Aeeuw;@>fK=mQpnYn;x5{@>{>EkEGemqA7vxy`+j({#}-i8 z4-{%-{C-k~r~Zqhfa_}}MBR+|V#bSU1xAyzCYnz?<>6!UrFdu{#z(x`>&M#a8<61@lDBfVtpl2m@1*1St*W8xJ)6S@) z=#QdXt4m|I$FC2+eEdB7yIgg3Gy5uGo&~cnO?`VnhBYF4T~uwUHeTX;T@fYM+d8Tb zVyz~&&Ne>Ggg(o_9GPZ0ct2Lt%lXn2hxKNK3z9sO>*>^)>aXX)8)y2q@tx2x@N<`4 za^A#L3f)dklDM?R)QGSf^H)>s+v~d75gARY>rX_Y*xytonz=uyTVoGLWJ^|jj zJ$y;<0EAJT`2MW>oF4z}2h4rRD>uhmK5))$ftss`6P0_O-NuC z@JLAT^Zp{e;=4D*9j~ZXYA>pP^cGhtop594YuD7<^0DaATtDOIZ2Yi`;%E8943PU7 z;76YbAU!W*Z6@i2vZVhWfH=bJP+zEr>{|4Rn>-vu%tNe5E6$s(aoN7A=SA>#~_ z78b#)W{`;^8*=_a*?z((+;w9jnqHRA@sYU?q;oQn!xQ-buG+lfY~VZ8JP&BVD@Q*Q z8mwdIIZB7;YQUY;&x8i+`B*K`JbSJNj0NhvC-m5z99ZH6$zwI{D`whR$g!$aYE`beBs=_E*m&+#hC6(6V;} zEwji%YVq_ajUF?=0@oc*djYEKd8o1>s{Fm6D!4&a6@X$uDCCeuLz<3f5kV?t;!sN5B-5FxIqE=G__ECmYLlv7<_$OFMp z0L+XkQcsaT{U|w)U>yIfwBJLe@TuN?+w9A$Ky8gF7sLz7?Z?Um9N!hqR{uo5JpKn8 Kx-j-f7ytmAcw_JY literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/disabled.tgs b/Telegram/Resources/animations/swipe_action/disabled.tgs new file mode 100644 index 0000000000000000000000000000000000000000..a4fb31cb257a14c35435758851a3d025af9900e4 GIT binary patch literal 731 zcmV<10wnz(iwFp_wn1b719Nw2aAjX)X>(y>Y-MCFYIARH0PR*yZ<|06{VQnCYqZOk z6K-jn=2EGZQuShSAU2Mv!JuX1CbInBJ0E}{rje>BQVvmK2Jf5Qoq03!|ZW>XO##67HitE|nluX&L*&egS}TMGU3_ zfcm7Y*^55!N9Jka~nHyfBjZQ3?BaM@oI@qNM6!JiBRO z&G13wxbTRcZWPVs;0e1RiG`N%m%!HQQ`7IW+ z3+1W{BprgJcX%7Fu|twjmWwG!P8|aNd-R7Sf)Li>+3n(m8LxmJG`ckp+40mB)Q3M( zyP;k#cku1%%cpDSSGKg3ac(hvXXf0P>Y)aQ4P?AdizXu)DQ_$2++lA~wV+;njo+2+ zysLvFP1gYOJ$D{>jD~;~OJcP6O{Oepc5~*I* zMk}OSvr8ZA#s)C{%HLSC+JSwyq7~?|V0j;c^$Nl2rQ)@c)e}~V=V_}GAKA+y&<*4H zAa=ty9`b(L^l94qDf7-)R43j`ilI%mB_}T(q@$T`7x;Z^GXvd%j{l|BfS0M+@{iQy N_!mon-Tz4o006{TY>WT^ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/mute.tgs b/Telegram/Resources/animations/swipe_action/mute.tgs new file mode 100644 index 0000000000000000000000000000000000000000..97d7bbf68c6e7f072da78d9af63c706aea8703c6 GIT binary patch literal 2586 zcmV+#3gz`5iwFp_wn1b719Nw2aAjX@b#!GeYIARH0PR{?Ph&?G{wwG{*Dck)@RnpU zd5K0NC7KuH2ZweC4;UlcolJ!I@AI8=$}1Nz4T0{Ns2gA3T27sPE8lj%b*Ej_QRhx| zv&m}Qo!)l8V(iEHYBinweR?%no_zEDiP2U&gEd&t;c+RC5As-@)ALSs9~a$eGOC;2 zbft?T``R1g6A%mg#VmA#=PlLw*CjR7Wgd&XAEX;_GZppNXmf{Gm%PKVug-G+s=u zS3LR%w+V;O;I}Qhq^kN7ba;&EgX?yAzh1Y)j{D45UW{)hqFqF1_v-@mO;;kbYheUW zEHAot#G2Y6@fuycXOS75OWhHw(QRCW7(`aQ^X!b32cNw)+C<0S&6A&f)YTSz9q}<| zXR`ZLuYpKW6R^cw++z}AAdHdRu=vs85C_h{Rj&njR)-lEsZSmmLtIpIA7 z?Y&jb>Oxz6)W$|-z4p#A(n(i?W3$oGMT|Hh1BW^75`hdBC~b}hQgAA(#iz*~tNJm# zwkfw6$Jz&u$D=szfp)0MfkZ>70CY?+#AGwxfLE;w_}2KDvgn=Hwt!kkkQt`aF@hc1 zWMXa)@9G0drTX|9qDm&JkaTt=oF0@o;&p_yh`t`;DlV%&2AvZx@lKbD!wLix1CP*l ztf@}dV&6_==_rvAC#zRBYtJ-Wh$kznlYf!b5Ra@UA{{`{aW3lsBzt%sTkGo?Q-*y+ zt34y^$b5i49+veX=>QGvEf)BK0Z0^^VReOHdslT1P?(yuDdFvelaCS(UTts+oU4Jk z6p#f#!Ay9Wa3Jggq9ZYfXogeCo~^3nR}@VNUcq5y&z=plL4lct7!QZ%Lm^LL*c-qd z+Kj!#{>+{ZQ#l1k}}INVQmxl6VE7lr)|k?LkE@2)i63gEXFq% z({mt1YX|v8h*dh_;GqY`O`{`fxLal!8*H}vU~9d zy)UpHiam(TzOZ;#^G%J0m+4Ny)+#efk>kdt7j7Ch;@|L=H@7xO}(C*fMz}6Cj7%TM3(aV-W2MWjip?jv;JTG zd)EvY;h$EE`Q_v(S0bzaa&BLobMr57ZX1rxxxeuX902ymM1QH(r;txQ$hK0WmA|DH)CH=i^Fi2^_hPcsj z@h=XM{KRmmGj!-wP}IxIQWD3@4~lbb3nlX)*$DN z&p04Ib%0BI6jZC-G0A~Pd4u{UY%7j6fXDzINf3HeKnYno@_Bc3`#Eq~ARnWLJJ6n5 z)a_&RU=z9~h5tKE=&e(Fv2>yhXQMzU2b;5^E+>MFy{Bcgp3-O zhALrdzfcW`63{v7Tyl8`oO8i(ky}g|qtv4D+}rcq-1XXVR|9DqQa1&aOf~P~vR?&n z>fCLg6F@6b?@bv%c?+rEBWs&UQeU)M?h|tpGLa~xZ=9qp86(xR7;z9f%dBVv&F&L2 z^TJZ3O9D@5P|LuJRASBCrHNeF0=fx=r_0jR$vLZ}eJ!nw&fN0JJ5{ws?M)JICAg(N z(Y7L9zqXRuCQJ7ej4$;ht;$hbhTbQf8k5Sx8sJLC+}>c*1#ez}ZQh9zx4T^U^GZFv z;g&~3NN4cY>kzVNdn`ebTCpsOwqzZ7O)GViy_ipG^W5ZiC5se7ZMJdPN_i98u+&ne zMiXgC1d`lw51|Thh#cGLh3JB5@YRLaDFw0&zUP2tRD@ZwjIv9^+gnB@T_&}sR^CGX+OEt+Er~U;v>b`ApDfnJd|mc?1p|_>vNv2ZzV^GY@Nkua(H* zL`KEY^_N&kmf(#wbSnuZGFCdEm9PFNp0k(e6T}rb0mvL&EVrFOer85_@+uKp4Z}*! zMBqrE+V1}%b`0MKty3#NqWZ*m2@Z*(0mTT(t-vEE{b{)ajl@&My3y{yEJbm;2w(t~ zt|(E2Ae_95ftXDBv*A3Dn(LK}Kw9e(Jfh@W78XEBibRhHHvvlJk`gJHk7N!0jje;O zt-Kh6T$)tEliQiLigkFo5)P14W`0BM3Q5d^Sh1jh)rCcr*xY-E{Mb8UWNKcH6KGU7 zuq!bSpObR%C$>e5Y^sW}rORKVeZ1&Wj!`ijba9H53<%^LreO!`*ikIS4w8fz0#+i@ zLu+^gCm#xrSXMh!O7R%y;pAQ}P99dx~65NDy)KiwTMeM()`F z0q!AC#y}z|K-a5?BbZtW83>`)(0cz>8O4+Lrl0fq7ApIU&v#J53Qr&^=cJ&;2&oYH zU+#6DQT0pC+H+KDN?55ZM%3owiu5xYib_d}FDPZPT+>U%&p&8^A~~pM-c>sg{593R wqgT98`s-@uZLem&o6mlkUQa&sESfjCe#ZQ4yttH~7LV@!3n=%QZT~C)0A_yp^8f$< literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/pin.tgs b/Telegram/Resources/animations/swipe_action/pin.tgs new file mode 100644 index 0000000000000000000000000000000000000000..5fbeedbc4fb7a0727d4bc080c5efa94bd42a7119 GIT binary patch literal 1387 zcmV-x1(f<9iwFp_wn1b719Nw2aAjX`X>KlRb8l_{?O0838#fUBE5e?+1c%?$EoswS zilRV+UIZN!TaEEmy64f8o*pU|1q8~SF=>M=???%K%uCKH9tZnCATa71EQLScQ=M7)q9j2EXj4;-H zh0)%|FP~SdY@==R+-=MLZ~14I7u}Yne`1#4Uo$wPFtaDhhYik_DKY06x)wp^pG2B=9%IqU~iJSciS+ba_*TBRYW(DHh+coKl&9 zNc7+7eeaXMXRo&<(1(mrWMjqVN1&+K)CtPEplo}TKVTIQnlczY*3 z0;|1|!Mdklzo2=Hf3JBA$zwp;#cVe(PZ-BqeQFTnvMYJp6fp8cyQumt_7M-ISjmu+ zMn@@C(qx$`DU2mkdCg=}6ONrrq93PXk{_nY1@X6kr+MSashMY6dE@}heqpl-w+6VA zu`+66MdM{4Q70q4^+y5|q*1I9wT$dUkd#*hBdr&~p_CZh);h_gw5YX~Iz8GIY`vyaG8 zsm;~?*IBp7oVwoN?#+*H-;I8sUv=AN^c$DVQ9XL!Y(Fz2EKntHW~+9d^T)OFW>sa= zCuLX^t}ENv{powm7_JZo^{mvpgBD~p?9TgUwp%Q(;1KH!`qObyKckul&K_WwVM0G8 zlwQGH;x~Ue#K1;xhZZp|?QCeq?Gy8%nQhveZL_X8XIYdpk&bpnKw0K7Ct%k#CJ?Wtanvpm_b@7<$4mK5vM+_)~K_>6>2RCkB5I2CmQ|NJUSPl9L4RnUOOmSn2TWIbi~Xp&Wf<%F%t`K*v$PHywlkl0`#r~WWhq9E26_TbwEUdf1Yr?hMRx} zE@uYBErGn8G@0-Q^o>+_O3IMEM-vIy5QOs#CYnlaB9T78xF_S<2V{T>E85bepk46> z5-qLH#OV+o_ax4hI>YoEtrY>8$(9g2Whf#2~Q;!`DE7VSbnzAR{57Yr=IN*3#w>PugY zJq?>8#OHP27zDu@y4c~%5OeQm9PJlr4N!0I literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/read.tgs b/Telegram/Resources/animations/swipe_action/read.tgs new file mode 100644 index 0000000000000000000000000000000000000000..59b258677c082b49dd013be8624c0000c5c7a25f GIT binary patch literal 1860 zcmV-K2fO$miwFp_wn1b719Nw2aAjX|WnpA4YIARH0PR`HZW~7s{S{%(J&oQM-fYM5 zC5Iphav^x2CCVbSP$20ff}wxUtLh$dh8iuFEhi2Fi=3J2n%esH>uUN_^G7pjy!0|Q zqPbjb=gs6(^8vD-R@?1z@#FGhu{nD6_DD%Bje-so8eGoH<)mD;$8^0F&FQ+C1SOi~ zbu+g~fd_$?M=xfgJBV?_}&R@9E2zEUf>qZnt zt_LGit+RAGiB_8|vvWetin6)of;>r=JZ=tj5l;P7rEp6YSFEu)n_n+V2`J^}!y0)m zw=CdVs8S0kHq}L!^}O5{RT5xONfhm-#If4ROk^P?y)9n5m!{L!da1Ri45muZld%c+ z196qk*ywb0Ox$A$@BBgU>`RudjE-K=$?hshkDW#cQacTa_cGbA*MTiyaJ&AvxSidK ze%998NAnKDyj_ynn=V?Hr8XA#196phd5?{Wdo1nq)-oz5T7w&9DJ`QAu+5a0kt#*b zy=qyD+zlyNsY2T89OjOM^g%{O>ZrRg=z)sd7ZzEU&D{|(YNN{}XXzD1qzvlKGp4L` zlt@mJC#+M+PP!0C0M?1IQyApXTV1@$f1Yp8jx-U}6`o%G@cQl1?~CK@YJK!8t&yYl z=-qnt@fr$eSQe-Ai_L=TK#Sw_qG8j6btm{=b>li<+@%R$SORP5{Ur}_I3UG~@o?U) z=htV;V+2Gxqw#EM6(8`6dWcZe6Bwi_*!xH=+@E43qvvBUQC&9ku@T=*%x~-Y)#h}y zzHDgDSj02I)w#N6IzHySb``8VHB!368JrEa5uJ#f3P-Uis_0;AFvG3k_3GrNdB$#V zhd8HT&Ka}CRL^=OG9?zN18O!PP&MG7&oSVNfmR<{h)=bks|ov53--W(F@qRLl6Ps% zMs~?B*=mRugfmJ8!!+4!R|WIsVYz!4lKSKJ-Oj#&105c405Wsfo2tgpQG9&!(6+b& zCQ%iyVJC#o z6op6EQ+i_9-Ve6bLf3e8(Q1ppC?p3u2_nl7=qX^$dl9ALmP#9G1RCPZO`%NL_hLcg z2Gc!9G6IQ%TDTxMxH9ahJ2TQ-*R#>~jC9Q&I&Pfl(0wKuJynqeZqTn=)HR?x&j$6Y z?Yctb)W3(6AppB1YUt4t8yovAz6H{J;PjGI;8c?2P!dieYBG(7gwg~jaIAoq;n_n| z3e@%`C>$cwI&~6F9XZ-q9jUUKMIlM!@LseiFSoi(fB`T_p?ho-9bt+M1V*k)30!*M z@R4kxb-sJ@0c3z#i{Ri3RSB*@7E?r+HKB$t6}BJ}m|UWTK;XE}W%`G38)U>04s)6_ zpXU?~IE>?H3vwOV4jeHeOo{eLqy01Rn?4i2Ev+90aFkhvQd5Of8Ut8mUJqJQNC_GY z0UWu=ed_=bO%4L4g)~roSJ29+^MWabQ$%cm=fx-g9|Nxr6}@y*76gwa}MDmd7i zX$&>VOuM-j+uX|Z zswxY7b0Ekk6yR!0wMCrP(4z}|489bOJt!fXGLd+RK#83^RBBE(7v8nBvQD(e{YKMjYrJZ(p2Cu{B1|r!}4zIGoD% z8iMFSy{>&L6F3kOedq)nB^?baqk7Gx-|h1nTff%@ap=964N{GtBsLeEmNtwfbhb#C ymSn#d7XAO1*zfn3*f*=o_sgrrNy}674StZt_~m?kUjE;1cKbK-1Rv*I8~^~o5r@YB literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/unarchive.tgs b/Telegram/Resources/animations/swipe_action/unarchive.tgs new file mode 100644 index 0000000000000000000000000000000000000000..f8a4f2108fd675f5d45cde343f1664cc72e38011 GIT binary patch literal 1191 zcmV;Y1X%kYiwFp_wn1b717m1mbaP*IZeem`XlZt3E^2dcZUF6B&2Hm15Pp?_XNeF= zk&=ANZreTev}iAZ531wTUOSc{D?f|CK+$vGp|7wlQ1sSkaNejhLrS77J2`CDaR9@J zIK!FY%#fT<6!XAt*??)TxnKdiD%rq90h=z_0Ga|C4ecEpC=;-WZ5G#HF&+K1I4?(& z>FpToE-x?1AYZM<)rt)+h5?)BpT{NA4~=-bfK~*o`pgEg8|cS;HG>hBc{yHG;2oq^ zX;o4)gCQPRfsdR9nL&4igWUrm)&VO!vRV>>Wa%V=BoN3HJMKvdLO|o9?%{DtooH8) z2F4x*@PlNkf*5U?DKQ~(>)-*^rLOfA8mxyl3JHVdo$#6wDabWh`C?4X{W&GG#z6`j z(j;Gw?VREqvR_M>x@l!|Ti8h^!D_;si+TpXY8uGo;vy7WVDJ}_dtyh!ZVOx4kQ`h* zs~7C?0QR@!KMlf+$0`MpM+#6&S{g``kw)gI&?7yCur&k^BQ~hYoAG*kNslP^xXnHG za+fVFxh8o`ZP*ZvC~OTnIeQzH%JO}&sEQlhUe^m?&%b;3{n^jsQB{;@A8`|&g=Zhi z;${g76WDiG`Fu5|iNoD@HD@$-XJZHSbAIA_&-H6s+meRO`pY#5DV`su^ZBk|K9u=# zG95uUPNN@9g;^|nRrq9y?k`XSahj$$)js?&`x8uX(Ah2{oRw9+OQP#_+5GwV+vcCm zf1AIc{j&KNsJ}N~9)ExQgW($bkh*}JC9DHw+03Q`g&h;Qy_&<`dte1+fg8wy9lf0OORWnfW@1H{6&6oZ9wV zAieeEkjtS<#AVWj7wAbn-f2Y{N(h#?uyodHVOO^YS>+t0%}_1uZMpH4yiwJj;nBL+ zBLa$ZPZkYgN*+Zj2o+bEvG}SjpAs*4oFzV+SyuV1aMGA3J`2aCnAjxluo1BlS2_t4 zPjqYpR$8?zDyvo*c$pFJ!bDf9bycTfc@MO|HDX8Hq*7^_#X=;o<#`f`%tT2VD-k0r zb)31b6R;lw*c(JWIJB!Sw7)CvW%tm2B&YRICqA%?sB>T^wtr$j+5`Iz7;i98V?}D? zcO_X*CG6-BS|-_W8>0I%#IU}3*F!&s4*J4mtLEq_3HdTE2+SoihNmeWMJ)%Q2kw+b zT1Ry>eA{+Na$+&jVZ#f8BGkk)$5^!=46x+yIwILt{Cq?UJ^y42c21S$qvFS^EN0`U zak0@kA!7|l2gk*@hl}DUaq)DEaa>>WsrycM1b&p8nlet7*5B}H^W|>ZZ+Il%#;t{_ zb5%{QqNaqCsXABHWGLEP#dfYQdRy^!Vg7W_^4c#Q;a)oGkS`s*1EEE$qKNnrTD=T(tvwc0J)J8-^c2V<#4vYIRwi?3dh^U!=~ z&Ks}1icQE05hJKOfuX z{iZn&x^1rSnsYqXcv#;yKR4%|Oxs-5=js;n_p96c-GV!L_fDke+wEevZO$)dZL^&J zy4djVhraoGg(qRKU(spp$l1#yrZ@haz6L_}dF`@7pRsMc|>i<_a`kzU=+ z8T5|+A+wV~*&H#o_#@5L4_t4oO4er^eGx!YTR*eMs#|zu?bl_(hn1nkotD0Tgf%sH zd1P%_ zR-y`#c8Wr54^KT|n(fv6Zc%)WXWjg?!BnqzWhGa|EYh&OYU)t?iT6XvnqORW6pKa| z;vpBEZLPA0qE~s)s@smlGisT{Rr(@q(7HjhQ}*a(9;+5-;!hX7pdWS9Ipt0b!#*<@ z1v5wF_1Ur8kM<*WV|J}k zl<4$UoCd>w3+lnPv!}2)oStgrPH(1OV*708Q!ReKK;LcVtL^*s=C)x_JVvwCogm$% zOsh9!-QN=y953Xopen`J!m2%n)gR^`7H8&dV08jgpF|@DOO7uzh(4%GQmIS0!S0Gw zB8o+XF@R!HkqQ*=)_rh7;d&6MYDlV~C}x!in;fuW8L}Qv`wm&Hat?5@BLxsCYlaXP8!0g~Ke%tc&3g}QD6b?L8#xyM%L zyY=1ghP!*?gLDNXtO*J4b1OJ0cX~1ZD9{68oPa$>-2v`R6FNRh(0+d)@Vak4kB~sK z$DPv0tKaVr7+jDehB1nI4O*Hxk|}^>%38T*&kBJ z7YdzlJI#th?8owbPq_BjXxolahuo-%AP z`;Ik@ISds5TTchGN1VSB{*H_yplKGWe;BO;oeq}qH|43E>FHqdYdYNWuWHb67&ato zbE+2)FXU&;V<+BwCM)9`5SDcL#MQ=>kAlLK&+M5?rrh>lvy=BsN7qPY4isSrr~6C> zcEjJPd#1ktARKR8gvk*oF zospz+PAJJAYE28hQ@K%LvSqRQzNVc9PQr(F@%SoeNN{@2GQ1A*av@}Ono#-pQs^+P zS${bMgZoUv(#o|BFGb;Tp`Sl38VVZ#$p;uvY~4U`#PayOJ{wVXJUrp#XkQ#Mg7Bl9;93R(S^f3=PTS^ zBA#UjEGHMAknamq=POGrY0Og_AOVNFXqDEXZUJj~3)t%ZA+YI!ZxZ`Idp*5{ z{|8YSo*fcP2Mpz1OFWRs!z&3JgaYfrHgkNS$eRGEgKR+q?cr1ufY7?=jj;%R4s(Q5 zrZMN68$SipSuYbzv)}D z`M3nZ0txnF_@H<+v0}?IBu%ox!2f+;6)CADw=B<$oV61n8MVY_lf`=VidDt-$L9Cu zyz$Db*tE@byc{*>ADds%_QPzsoQ%Jl+>RG#UwwO~6)B_92OSz*Zp!6dxh${v`p`D- z=goQ0Z8N!V&T*OH;!@lE(VTlSZF60(cT@EL*KB%!Mm)&7chY;bSd5p8=KSKaZEi<@ zj_3USUf~j`8D%+gll9F_(Kc3zmPzA0v6_rabyz+$G32uXjl9VDJey z;O>Q14%1@j3%W$d*{0prcFpZe%#8&s>hG-q;+_F0#GN4=l_5GKV{l7c4|-YU)#!V_ zwUP_tLu;aLb?svw1dx6guUo(Az-wM?cOb3X`mc5h4?N7QXler-C45;d<@Ya8wq{Yc zz%T_2SHbvUayJGP_oMmvZYj-Apu!?N#$_9^z6Ks4rj-$;Qcu|cOWJf%vvF0;6qWU{ z?MzZWk!WW*V{NxZRm;ixM;}u_IxvOnPhnG;g0%86w-CyhgX6Pus%Tbe>)Xz&Y+}0s ze*oB>o}Y>D;(ByHF1%vsnqTKYY_cqxy(`$tjm33Sr&!^)o_d$hb3dQb20>#J|R{`TxY z{~S=^Y40Mf0l_~-d#ddACM46hpwc{!eA;oNa>-C(RJbjv1M`R{Tw^Byv~2XWTD zTeb@5ap(3*1ik%DF5(la*gq}jvzzg-$luRL_t%pv$Xt32qs4Z(J4`LtmxkS4KH(a+tzY+qLu^{U0nv;pPq+V|y- zlcobs+HO1PpJ(?k=A*0jgI59k?t)d(`KLhK)AKL(Y6DwKvUMZjZ(H6Un|Zy1Z`BXf zJ)rujHz%nDRGN6N;`ugNO{9@cOff-?XwFBWAiK> zW###)7!*Z*%4S7*BU#xgmh%r_!S+7=X?W!?$BQ5swlHObm(@WJrgh3@djtSUX=qBT zoOp1o2_)|jf4dh2)F;N%X;<*f45(4o7+#?F&@6`^KqLsn4kKjdZd5u&X(1If!)H_o zsRByD3ZOpIiveo&mjem|P5}t&%sMBw*4UC`n2&%cf;XPoK&xWu`~`Zk%Ed>PbyQHb zAx1R^o(BAkA+#V|%0fZU_SB|v??Ph9?icCo&m9uRLo`sG9C^Tz%VwEF_T2!LI06Rx72qi;j*ks(Hz6+ zLf8zqn-P)}OIO4j7M!tuNyiHl3IeHjsX_`z6r2i?SIN60*nr5UY}(7?=ri)Tq0&cqX6Zw(3TtV%2>RR+;;I7r*&~o&2@!UO*jLJZRKZ!- zVf}N5*jEisyG5WqmE3`3GY%CpO^*FK<4WNsD4Swy90GKNa?h`1x}i!YLjK+yYlW^R z;V9U;J(FX508+=a@H$;T(gavF<3toV+G-fvMqLj+S}0@vR#iiL`BF}{;dpA z-1@dLA`)1Ctn9K%+%uY#TKq3X$+D0Y(EXyyY6&k|Rv9X%+W|9Q#Ua>plc2$0lgpej zA=WGzR3OIzchI(EE(T!|TTAQ(>E0ynlVVsma+pgk@8M6y9>^CWl5npmmQ5eY1m_Yo zl;3lX?!i|F)|Q9>Vgy7u0Nm~dKgi}`?hr`|98T&axCCEM(1>CstY7BorNj zJk#>W6QV;$(emUeORnjZbyiZ9;iu4F!!FCEtkr?oya>-HS4du6sYgVpaTRlM-XDmh zeB)dx=nx*jOaWPtk&1xF@;EZ`2x?=kD#aRZ8shGj0c$*Cek7Tr%Va=;mdL;Er{r%Lklb+mlTMT2zEm7 zIld&m@I_pNAr0;6q7}oo7u%d`p43GQ-I@$E3h4YbCOKuYz6=a8k(oia$SMjNgbd8E zKvCkyQjQ8>Le9mH654u>q?YiJ;TCi#T`M|+Skn;wn?NT ze6%2^7E!8e1QRsrEs=*bH^MuWsQDvB&WnG{PQTidKB$ ziM_|*hLod_pBvs*`R!VkHx``+T-+&WJ2rRB6G0oFTEGOH6FD(I*A~H;%MLz*dnAUN>p*h+klxtF6QF7%uauTDEx*D<*j3P9-jNCXw*dS;g_GAjm^g`f zqis6zW)E)$UJ>bWzzME$DZrT^RRB|fdn;K#*L*o|v6A&VEAM^a;>uf?Zzt;~oyy;e z1g^-JRYPWe{l#OGFSVHr6(o!WJM&&FpWlRm4H5%)V!{v;PVerV-rYI;?hfuxNAsKV R=ZlvQ{{wYhAM#^4006v}`rH5j literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/swipe_action/unread.tgs b/Telegram/Resources/animations/swipe_action/unread.tgs new file mode 100644 index 0000000000000000000000000000000000000000..97ce96a05f58bca90c77b71064a6148bf00279bc GIT binary patch literal 2084 zcmV+<2;27`iwFp_wn1b719Nw2aAjY0ZgOQ|WG-rRZ*BnXT1#)+I1v6TfzC3);hVX& z-N$7Q3+&?(_~0f^V>F2a+xsYr{P+E4NZFDjcayXZ5bUCGBn?Nyd3+qsQ2pHe*_<|B zc@>+sxtVWf&FSan186@jH=D)$hsE`Lee&k}6Rk)Y4IVHwxO^;^i*nhV^Yy-ME?3QI z&~3B0Yff=l;$lPF{MDR#GHr8JueUeg|Lt})pIxw!iwohNt=IF-x;Z_Yw$1hI(|pDE zpSQz{TU-f~eQHi!mGN$|I=`Mn#@%c+zuh#a5WZds+s8GVvgAKAc^@j1W;uW3hM??f zFxHJ|jNA=I&WN&dl-dcItg>^ho)%_vFAjMWFVDE%$XS$V# zh}%A~(552qY-FSzl>z=gh2I4QCxIM|a)D*Lth9@ugsi-^Lh6`8P(gbkS!IBtEf)j= zf1A}G^ZV(2+qXFWo5gu(u`?HjvG&$T4$d%ezJvP=JkhHbj5TF4&4QZtVzd#wb z1lVMaqQr!b$2(;*d&fCtt=+PGh1C>?W&^3#Y?Z~6GaS4Dxl%i`$Hv+NlD1}3Kn@{T zkc{$9Vu=u%oXcu4ftb{BMXz#XR|Km_+a1qWSZ>_VavN3FuG7~hr7gR9K(5rz^0Be@ zfTUf(HG&Dq!Z2l`VKWl~9&0$A7E;FrgS};nH_8(2c=j!aMRsq|TCa$<5x|qN?85=M zQnwb3jI{?O?PA%bxolN*g4krYSx@3vIk0faab&$$$%Y-zz?xo0mCXM0Y;$!&yeZw{ z>CJajq4!t zaBJ{oXF&dZegMWT2o3{e|nILz!x9+ zKGg+0vCo!}iUcIp=ZZkiE=tgIC0MF{s7f%&Z(TDTDXzYvLP-u=twUj8WT9-9WzCd_ z#rENen?u|QH&KOeE1Y~!_4Fv!IWA_z;C`;$OB`HFwG?;q_jAZ7(%bs$LM@4MFWh@P zotC|z^sCu@5ViI9%o$=ggM;*Nj2XJ{umSlfGjyRO)Hda}i*g$A;7fD_$ySW=-508+ zL5ZPjNuZ^^&njY_Pc*6n-s2|>Jya}7$(BFiibl!;)O2zC8dtsij<`Amo&N3PArBn@IcFo>LGh&?WC|pWgRA9wsU@wxgfnP}I zFanmC2saWdl@!8e2!o^6%6mN{|pSXu1HW`Sxq9=qg6yx^#iCW$pyuh7+VGZe}S=T;` z4I#Y%mJ_2y+7+z?PaZ3aAE|b^fF=b_2Uv$%%+5h2&#!0$FG)*C#Jx^C$-Hz6j=A** zI5O6Ocz`83J{8DGrF0Y=n};~g-xSAVway5F-C+bHmpRu;#;pj}oO&51rabd709FDC zK3IpOTQW0y&@nd1?BaT)B#d1EPJyQ-7XWa{0I^n%0I*YJ(<0VnMR-WL1=vI>VmGsn zI==*d0{~>|VMLJ33re;H&_+@lpF&w)(;9jwc5{LUIZ0yM(OBz6OwFRn{PkeSzP2O>Cc3Do-V0JWCz*-1K zB@=*U)5~ke!fs0z+Ob~j*U-Z8l3bCwA{P(Zl?0?1#gvWAoiZ04=ux0e-vG3nGLe)3N-yCy za!u4|HHR3Z*L$ykuIgYg$H2X)!&wlUdI6jl2TXMie{~=T)g(iN9)aPljOlu9Hh# zQh<_tI0jgcR2@A09`RoSp??_=BFPQT3z*;rpI(3)Km{4oz<>42i{?WHu3i%d8ntQS zGX@9j**NWet+AQA%jv5^lo|cesMd$m`G^8-RC5I=yMXrZ)U5H<$sz? O@Baa../../animations/star_reaction/effect1.tgs ../../animations/star_reaction/effect2.tgs ../../animations/star_reaction/effect3.tgs + + ../../animations/swipe_action/archive.tgs + ../../animations/swipe_action/unarchive.tgs + ../../animations/swipe_action/delete.tgs + ../../animations/swipe_action/disabled.tgs + ../../animations/swipe_action/mute.tgs + ../../animations/swipe_action/unmute.tgs + ../../animations/swipe_action/pin.tgs + ../../animations/swipe_action/unpin.tgs + ../../animations/swipe_action/read.tgs + ../../animations/swipe_action/unread.tgs diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 4027da23ee..2d4d01fda5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -789,3 +789,5 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px); dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) { minWidth: 128px; } + +dialogsSwipeActionSize: 20px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5836207a1f..9b1492e9a2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/options.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "mainwindow.h" #include "mainwidget.h" #include "storage/storage_account.h" @@ -808,6 +809,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto ms = crl::now(); const auto childListShown = _childListShown.current(); auto context = Ui::PaintContext{ + .swipeContext = _swipeContext, .st = _st, .topicJumpCache = _topicJumpCache.get(), .folder = _openedFolder, @@ -4992,4 +4994,74 @@ rpl::producer InnerWidget::openBotMainAppRequests() const { return _openBotMainAppRequests.events(); } +void InnerWidget::setSwipeContextData(Ui::Controls::SwipeContextData data) { + _swipeContext.data = std::move(data); + if (_swipeContext.data.msgBareId) { + constexpr auto kStartAnimateThreshold = 0.32; + constexpr auto kResetAnimateThreshold = 0.24; + if (_swipeContext.data.ratio > kStartAnimateThreshold) { + if (_swipeContext.icon + && !_swipeContext.icon->frameIndex() + && !_swipeContext.icon->animating()) { + _swipeContext.icon->animate( + [=] { update(); }, + 0, + _swipeContext.icon->framesCount()); + } + } else if (_swipeContext.data.ratio < kResetAnimateThreshold) { + if (_swipeContext.icon && _swipeContext.icon->frameIndex()) { + _swipeContext.icon->jumpTo(0, [=] { update(); }); + } + } + update(); + } +} + +int64 InnerWidget::calcSwipeKey(int top) { + top -= dialogsOffset(); + for (auto it = _shownList->begin(); it != _shownList->end(); ++it) { + const auto row = it->get(); + const auto from = row->top(); + const auto to = from + row->height(); + if (top >= from && top < to) { + if (const auto peer = row->key().peer()) { + return peer->id.value; + } + return 0; + } + } + return 0; +} + +void InnerWidget::prepareSwipeAction( + int64 key, + Dialogs::Ui::SwipeDialogAction action) { + if (key) { + auto name = (action == Dialogs::Ui::SwipeDialogAction::Mute) + ? u"swipe_mute"_q + : (action == Dialogs::Ui::SwipeDialogAction::Pin) + ? u"swipe_pin"_q + : (action == Dialogs::Ui::SwipeDialogAction::Read) + ? u"swipe_read"_q + : (action == Dialogs::Ui::SwipeDialogAction::Archive) + ? u"swipe_archive"_q + : (action == Dialogs::Ui::SwipeDialogAction::Delete) + ? u"swipe_delete"_q + : u"swipe_disabled"_q; + _swipeLottieIcon = Lottie::MakeIcon({ + .name = std::move(name), + .sizeOverride = Size(st::dialogsSwipeActionSize), + }); + _swipeContext.icon = _swipeLottieIcon.get(); + _swipeContext.action = action; + } else { + if (_swipeContext.icon) { + _swipeContext = {}; + } + if (_swipeLottieIcon) { + _swipeLottieIcon.reset(); + } + } +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 2500f43d1f..846feac821 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" #include "base/timer.h" #include "dialogs/dialogs_key.h" +#include "dialogs/ui/dialogs_swipe_context.h" #include "data/data_messages.h" #include "ui/dragging_scroll_manager.h" #include "ui/effects/animations.h" @@ -25,6 +26,10 @@ namespace MTP { class Error; } // namespace MTP +namespace Lottie { +class Icon; +} // namespace Lottie + namespace Main { class Session; } // namespace Main @@ -34,6 +39,9 @@ class IconButton; class PopupMenu; class FlatLabel; struct ScrollToRequest; +namespace Controls { +enum class SwipeDialogAction; +} // namespace Controls } // namespace Ui namespace Window { @@ -208,6 +216,10 @@ public: [[nodiscard]] rpl::producer openBotMainAppRequests() const; + void setSwipeContextData(Ui::Controls::SwipeContextData data); + [[nodiscard]] int64 calcSwipeKey(int top); + void prepareSwipeAction(int64 key, Dialogs::Ui::SwipeDialogAction); + protected: void visibleTopBottomUpdated( int visibleTop, @@ -611,6 +623,9 @@ private: rpl::event_stream<> _refreshHashtagsRequests; rpl::event_stream _openBotMainAppRequests; + Dialogs::Ui::SwipeContext _swipeContext; + std::unique_ptr _swipeLottieIcon = nullptr; + RowDescriptor _chatPreviewRow; bool _chatPreviewScheduled = false; std::optional _chatPreviewTouchGlobal; diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp new file mode 100644 index 0000000000..90ed1fc032 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp @@ -0,0 +1,29 @@ +/* +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 "dialogs/dialogs_swipe_action.h" + +#include "dialogs/ui/dialogs_swipe_context.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "main/main_session.h" +#include "menu/menu_mute.h" + +namespace Dialogs { + +void PerformSwipeDialogAction( + not_null peer, + Ui::SwipeDialogAction action) { + if (action == Dialogs::Ui::SwipeDialogAction::Mute) { + const auto history = peer->owner().history(peer); + MuteMenu::ThreadDescriptor(history).updateMutePeriod( + std::numeric_limits::max()); + } +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h new file mode 100644 index 0000000000..9dca99277c --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h @@ -0,0 +1,22 @@ +/* +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 + +class PeerData; + +namespace Dialogs::Ui { +enum class SwipeDialogAction; +} // namespace Dialogs::Ui + +namespace Dialogs { + +void PerformSwipeDialogAction( + not_null peer, + Ui::SwipeDialogAction action); + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 83ab9665e9..b00963bc91 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_widget.h" +#include "base/call_delayed.h" #include "base/qt/qt_key_modifiers.h" #include "base/options.h" #include "dialogs/ui/chat_search_in.h" @@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_suggestions.h" #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_search_from_controllers.h" +#include "dialogs/dialogs_swipe_action.h" #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_item.h" @@ -688,32 +690,64 @@ void Widget::setupSwipeBack() { Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation != 0) { - if (!_swipeBackData.callback) { - _swipeBackData = Ui::Controls::SetupSwipeBack( - this, - []() -> std::pair { - return { - st::historyForwardChooseBg->c, - st::historyForwardChooseFg->c, - }; - }, - _swipeBackMirrored, - _swipeBackIconMirrored); + if (data.translation < 0) { + if (_inner) { + _inner->setSwipeContextData(std::move(data)); + } + } else { + if (!_swipeBackData.callback) { + _swipeBackData = Ui::Controls::SetupSwipeBack( + this, + []() -> std::pair { + return { + st::historyForwardChooseBg->c, + st::historyForwardChooseFg->c, + }; + }, + _swipeBackMirrored, + _swipeBackIconMirrored); + } + _swipeBackData.callback(data); } - _swipeBackData.callback(data); return; } else { if (_swipeBackData.lifetime) { _swipeBackData = {}; } + if (_inner) { + _inner->setSwipeContextData({}); + _inner->update(); + } } - }, [=](int, Qt::LayoutDirection direction) { + }, [=](int top, Qt::LayoutDirection direction) { _swipeBackIconMirrored = false; _swipeBackMirrored = false; if (_childListShown.current()) { return Ui::Controls::SwipeHandlerFinishData(); } const auto isRightToLeft = direction == Qt::RightToLeft; + if (!isRightToLeft && _inner) { + if (const auto key = _inner->calcSwipeKey(top)) { + const auto action = Dialogs::Ui::SwipeDialogAction::Mute; + _inner->prepareSwipeAction(key, action); + return Ui::Controls::SwipeHandlerFinishData{ + .callback = [=, session = &session()] { + auto callback = [=, peerId = PeerId(key)] { + const auto peer = session->data().peer(peerId); + PerformSwipeDialogAction(peer, action); + }; + base::call_delayed( + st::slideWrapDuration, + session, + std::move(callback)); + }, + .msgBareId = key, + .speedRatio = 1., + .reachRatioDuration = crl::time(st::slideWrapDuration), + .provideReachOutRatio = true, + }; + } + } if (controller()->openedFolder().current()) { if (!isRightToLeft) { return Ui::Controls::SwipeHandlerFinishData(); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 39a8e3826c..ebde34c6a5 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -28,12 +28,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_unread_things.h" #include "history/view/history_view_item_preview.h" #include "history/view/history_view_send_action.h" +#include "lang/lang_instance.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "main/main_session.h" #include "storage/localstorage.h" #include "support/support_helper.h" #include "ui/empty_userpic.h" #include "ui/painter.h" +#include "ui/rect.h" #include "ui/power_saving.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" @@ -66,6 +69,54 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_"; return !history->isForum(); } +[[nodiscard]] QString SwipeActionText(Dialogs::Ui::SwipeDialogAction type) { + return (type == Dialogs::Ui::SwipeDialogAction::Archive) + ? tr::lng_settings_swipe_archive(tr::now) + : (type == Dialogs::Ui::SwipeDialogAction::Delete) + ? tr::lng_settings_swipe_delete(tr::now) + : (type == Dialogs::Ui::SwipeDialogAction::Read) + ? tr::lng_settings_swipe_read(tr::now) + : (type == Dialogs::Ui::SwipeDialogAction::Pin) + ? tr::lng_settings_swipe_pin(tr::now) + : tr::lng_settings_swipe_mute(tr::now); +} + +const style::font &SwipeActionFont( + Dialogs::Ui::SwipeDialogAction action, + int availableWidth) { + struct Entry final { + Dialogs::Ui::SwipeDialogAction action; + QString langId; + style::font font; + }; + static auto Fonts = std::vector(); + for (auto &entry : Fonts) { + if (entry.action == action) { + if (entry.langId == Lang::GetInstance().id()) { + return entry.font; + } + } + } + constexpr auto kNormalFontSize = 13; + constexpr auto kMinFontSize = 5; + for (auto i = kNormalFontSize; i >= kMinFontSize; --i) { + auto font = style::font( + style::ConvertScale(i, style::Scale()), + st::semiboldFont->flags(), + st::semiboldFont->family()); + if (font->width(SwipeActionText(action)) <= availableWidth + || i == kMinFontSize) { + Fonts.emplace_back(Entry{ + .action = action, + .langId = Lang::GetInstance().id(), + .font = std::move(font), + }); + return Fonts.back().font; + } + } + Unexpected("SwipeActionFont: can't find font."); +} + void PaintRowTopRight( QPainter &p, const QString &text, @@ -344,11 +395,25 @@ void PaintRow( draft = nullptr; } + const auto history = entry->asHistory(); + const auto thread = entry->asThread(); + const auto sublist = entry->asSublist(); + auto bg = context.active ? st::dialogsBgActive : context.selected ? st::dialogsBgOver : context.currentBg; + auto swipeTranslation = 0; + if (history + && history->peer->id.value == context.swipeContext.data.msgBareId) { + if (context.swipeContext.data.translation != 0) { + swipeTranslation = context.swipeContext.data.translation * -2; + } + } + if (swipeTranslation) { + p.translate(-swipeTranslation, 0); + } p.fillRect(geometry, bg); if (!(flags & Flag::TopicJumpRipple)) { auto ripple = context.active @@ -357,10 +422,6 @@ void PaintRow( row->paintRipple(p, 0, 0, context.width, &ripple->c); } - const auto history = entry->asHistory(); - const auto thread = entry->asThread(); - const auto sublist = entry->asSublist(); - if (flags & Flag::SavedMessages) { EmptyUserpic::PaintSavedMessages( p, @@ -821,6 +882,54 @@ void PaintRow( + (tag->width() / style::DevicePixelRatio()); } } + if (swipeTranslation) { + p.translate(swipeTranslation, 0); + const auto swipeActionRect = QRect( + geometry.x() + geometry.width() - swipeTranslation, + geometry.y(), + swipeTranslation, + geometry.height()); + p.setClipRegion(swipeActionRect); + p.fillRect(swipeActionRect, st::attentionButtonFg); + if (context.swipeContext.data.reachRatio) { + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgActive); + const auto r = swipeTranslation + * context.swipeContext.data.reachRatio; + const auto offset = st::dialogsSwipeActionSize + + st::dialogsSwipeActionSize / 2.; + p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r); + } + const auto iconOffset = (geometry.height() + - st::dialogsSwipeActionSize) / 2; + const auto topTranslation = iconOffset / 2.; + p.translate(0, -topTranslation); + if (context.swipeContext.icon) { + context.swipeContext.icon->paint( + p, + rect::right(geometry) + - iconOffset + - st::dialogsSwipeActionSize, + iconOffset, + st::premiumButtonFg->c); + } + { + p.setPen(st::premiumButtonFg); + p.setBrush(Qt::NoBrush); + const auto left = rect::right(geometry) + - iconOffset * 2 + - st::dialogsSwipeActionSize; + const auto availableWidth = geometry.width() - left; + p.setFont( + SwipeActionFont(context.swipeContext.action, availableWidth)); + p.drawText( + QRect(left, 0, availableWidth, geometry.height()), + SwipeActionText(context.swipeContext.action), + style::al_bottom); + } + p.translate(0, topTranslation); + p.setClipRegion(QRegion()); + } } } // namespace diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index e014e657f5..9b905eac82 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "dialogs/ui/dialogs_swipe_context.h" #include "ui/cached_round_corners.h" namespace style { @@ -18,9 +19,6 @@ namespace st { extern const style::DialogRow &defaultDialogRow; } // namespace st -namespace Ui { -} // namespace Ui - namespace Data { class Forum; class Folder; @@ -57,6 +55,7 @@ struct TopicJumpCache { struct PaintContext { RightButton *rightButton = nullptr; std::vector *chatsFilterTags = nullptr; + SwipeContext swipeContext; not_null st; TopicJumpCache *topicJumpCache = nullptr; Data::Folder *folder = nullptr; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h new file mode 100644 index 0000000000..bbfc81d018 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h @@ -0,0 +1,35 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/controls/swipe_handler_data.h" + +namespace Lottie { +class Icon; +} // namespace Lottie + +namespace Dialogs::Ui { + +using namespace ::Ui; + +enum class SwipeDialogAction { + Mute, + Pin, + Read, + Archive, + Delete, + Disabled, +}; + +struct SwipeContext { + ::Ui::Controls::SwipeContextData data; + Lottie::Icon *icon = nullptr; + SwipeDialogAction action; +}; + +} // namespace Dialogs::Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 321fcbdbc9..1e66b314a3 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -103,6 +103,7 @@ PRIVATE dialogs/ui/chat_search_in.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h + dialogs/ui/dialogs_swipe_context.h dialogs/ui/top_peers_strip.cpp dialogs/ui/top_peers_strip.h From b95035e7a25c612119c4f09601d5fafd06f449b7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 16 Mar 2025 22:28:26 +0300 Subject: [PATCH 061/122] Added initial ability to change swipe dialog action from chat settings. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/core/core_settings.cpp | 19 ++++++- Telegram/SourceFiles/core/core_settings.h | 7 +++ .../SourceFiles/dialogs/dialogs_widget.cpp | 3 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 2 +- .../SourceFiles/settings/settings_chat.cpp | 57 +++++++++++++++++++ 6 files changed, 85 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7de5a48e25..0763000c0b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1180,6 +1180,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_restart_now" = "Restart"; "lng_settings_restart_later" = "Later"; +"lng_settings_swipe_subsection" = "Chat list swipe gesture"; "lng_settings_swipe_mute" = "Mute"; "lng_settings_swipe_unmute" = "Unmute"; "lng_settings_swipe_pin" = "Pin"; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 60eac9de30..08e04273d8 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -240,7 +240,7 @@ QByteArray Settings::serialize() const { + Serialize::stringSize(_customFontFamily) + sizeof(qint32) * 3 + Serialize::bytearraySize(_tonsiteStorageToken) - + sizeof(qint32) * 7; + + sizeof(qint32) * 8; auto result = QByteArray(); result.reserve(size); @@ -401,7 +401,8 @@ QByteArray Settings::serialize() const { << qint32(_recordVideoMessages ? 1 : 0) << SerializeVideoQuality(_videoQuality) << qint32(_ivZoom.current()) - << qint32(_systemDarkModeEnabled.current() ? 1 : 0); + << qint32(_systemDarkModeEnabled.current() ? 1 : 0) + << qint32(_swipeDialogAction); } Ensures(result.size() == size); @@ -530,6 +531,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0; quint32 videoQuality = SerializeVideoQuality(_videoQuality); quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0; + quint32 swipeDialogAction = quint32(_swipeDialogAction); stream >> themesAccentColors; if (!stream.atEnd()) { @@ -858,6 +860,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> systemDarkModeEnabled; } + if (!stream.atEnd()) { + stream >> swipeDialogAction; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -1079,6 +1084,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _recordVideoMessages = (recordVideoMessages == 1); _videoQuality = DeserializeVideoQuality(videoQuality); _chatFiltersHorizontal = (chatFiltersHorizontal == 1); + _swipeDialogAction = Dialogs::Ui::SwipeDialogAction(swipeDialogAction); } QString Settings::getSoundPath(const QString &key) const { @@ -1470,6 +1476,7 @@ void Settings::resetOnLastLogout() { _recordVideoMessages = false; _videoQuality = {}; _chatFiltersHorizontal = false; + _swipeDialogAction = Dialogs::Ui::SwipeDialogAction::Disabled; _recentEmojiPreload.clear(); _recentEmoji.clear(); @@ -1657,4 +1664,12 @@ void Settings::setChatFiltersHorizontal(bool value) { _chatFiltersHorizontal = value; } +Dialogs::Ui::SwipeDialogAction Settings::swipeDialogAction() const { + return _swipeDialogAction; +} + +void Settings::setSwipeDialogAction(Dialogs::Ui::SwipeDialogAction action) { + _swipeDialogAction = action; +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 91da7945a2..9492037fa2 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings_proxy.h" #include "media/media_common.h" +#include "dialogs/ui/dialogs_swipe_context.h" #include "window/themes/window_themes_embedded.h" #include "ui/chat/attach/attach_send_files_way.h" #include "base/flags.h" @@ -946,6 +947,9 @@ public: [[nodiscard]] static PlaybackSpeed DeserializePlaybackSpeed( qint32 speed); + [[nodiscard]] Dialogs::Ui::SwipeDialogAction swipeDialogAction() const; + void setSwipeDialogAction(Dialogs::Ui::SwipeDialogAction); + void resetOnLastLogout(); private: @@ -1086,6 +1090,9 @@ private: bool _recordVideoMessages = false; + Dialogs::Ui::SwipeDialogAction _swipeDialogAction + = Dialogs::Ui::SwipeDialogAction::Disabled; + QByteArray _photoEditorBrush; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index b00963bc91..c143945410 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -728,7 +728,8 @@ void Widget::setupSwipeBack() { const auto isRightToLeft = direction == Qt::RightToLeft; if (!isRightToLeft && _inner) { if (const auto key = _inner->calcSwipeKey(top)) { - const auto action = Dialogs::Ui::SwipeDialogAction::Mute; + const auto action + = Core::App().settings().swipeDialogAction(); _inner->prepareSwipeAction(key, action); return Ui::Controls::SwipeHandlerFinishData{ .callback = [=, session = &session()] { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index ebde34c6a5..9cb8692927 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -928,7 +928,7 @@ void PaintRow( style::al_bottom); } p.translate(0, topTranslation); - p.setClipRegion(QRegion()); + p.setClipping(false); } } diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index a02b6ad20b..b6e8f495e4 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -1273,6 +1273,62 @@ void SetupChatBackground( }, adaptive->lifetime()); } +void SetupChatListSwipe( + not_null controller, + not_null container) { + Ui::AddDivider(container); + Ui::AddSkip(container); + Ui::AddSubsectionTitle(container, tr::lng_settings_swipe_subsection()); + + using Type = Dialogs::Ui::SwipeDialogAction; + const auto group = std::make_shared>( + Core::App().settings().swipeDialogAction()); + container->add( + object_ptr( + container, + group->value() | rpl::map([](Type value) { + return ((value == Dialogs::Ui::SwipeDialogAction::Mute) + ? tr::lng_settings_swipe_mute + : (value == Dialogs::Ui::SwipeDialogAction::Pin) + ? tr::lng_settings_swipe_pin + : (value == Dialogs::Ui::SwipeDialogAction::Read) + ? tr::lng_settings_swipe_read + : (value == Dialogs::Ui::SwipeDialogAction::Archive) + ? tr::lng_settings_swipe_archive + : tr::lng_settings_swipe_disabled)(); + }) | rpl::flatten_latest(), + st::settingsButtonNoIcon) + )->setClickedCallback([=] { + controller->uiShow()->showBox(Box([=](not_null box) { + box->setTitle(tr::lng_settings_swipe_subsection()); + const auto addRadio = [&](Type value, tr::phrase<> phrase) { + box->verticalLayout()->add( + object_ptr>( + box->verticalLayout(), + group, + value, + phrase(tr::now), + st::settingsSendType), + st::settingsSendTypePadding); + }; + addRadio(Type::Mute, tr::lng_settings_swipe_mute); + addRadio(Type::Pin, tr::lng_settings_swipe_pin); + addRadio(Type::Read, tr::lng_settings_swipe_read); + addRadio(Type::Archive, tr::lng_settings_swipe_archive); + addRadio(Type::Delete, tr::lng_settings_swipe_delete); + addRadio(Type::Disabled, tr::lng_settings_swipe_disabled); + box->addButton(tr::lng_settings_save(), [=] { + Core::App().settings().setSwipeDialogAction( + group->current()); + Core::App().saveSettingsDelayed(); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + })); + }); + Ui::AddSkip(container); +} + void SetupDefaultThemes( not_null window, not_null container) { @@ -1803,6 +1859,7 @@ void Chat::setupContent(not_null controller) { SetupThemeSettings(controller, content); SetupCloudThemes(controller, content); SetupChatBackground(controller, content); + SetupChatListSwipe(controller, content); SetupStickersEmoji(controller, content); SetupMessages(controller, content); Ui::AddDivider(content); From d7a89ef1222b8bc774a1b0e49dfc61ac719a7efa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 16 Mar 2025 23:37:37 +0300 Subject: [PATCH 062/122] Added implementation for all swipe dialog actions. --- .../dialogs/dialogs_inner_widget.cpp | 14 +- .../dialogs/dialogs_swipe_action.cpp | 120 +++++++++++++++++- .../dialogs/dialogs_swipe_action.h | 22 +++- .../SourceFiles/dialogs/dialogs_widget.cpp | 26 ++-- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 28 ++-- .../dialogs/ui/dialogs_swipe_context.h | 13 ++ .../SourceFiles/window/window_peer_menu.cpp | 74 ++++++----- .../SourceFiles/window/window_peer_menu.h | 7 + 8 files changed, 226 insertions(+), 78 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 9b1492e9a2..3cd6952ce9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_search_from_controllers.h" #include "dialogs/dialogs_search_tags.h" +#include "dialogs/dialogs_swipe_action.h" #include "history/view/history_view_context_menu.h" #include "history/history.h" #include "history/history_item.h" @@ -5037,17 +5038,8 @@ void InnerWidget::prepareSwipeAction( int64 key, Dialogs::Ui::SwipeDialogAction action) { if (key) { - auto name = (action == Dialogs::Ui::SwipeDialogAction::Mute) - ? u"swipe_mute"_q - : (action == Dialogs::Ui::SwipeDialogAction::Pin) - ? u"swipe_pin"_q - : (action == Dialogs::Ui::SwipeDialogAction::Read) - ? u"swipe_read"_q - : (action == Dialogs::Ui::SwipeDialogAction::Archive) - ? u"swipe_archive"_q - : (action == Dialogs::Ui::SwipeDialogAction::Delete) - ? u"swipe_delete"_q - : u"swipe_disabled"_q; + const auto peer = session().data().peer(PeerId(key)); + auto name = ResolveSwipeDialogLottieIconName(peer, action, _filterId); _swipeLottieIcon = Lottie::MakeIcon({ .name = std::move(name), .sizeOverride = Size(st::dialogsSwipeActionSize), diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp index 90ed1fc032..9409b3a1e5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp @@ -8,22 +8,134 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_swipe_action.h" #include "dialogs/ui/dialogs_swipe_context.h" +#include "apiwrap.h" +#include "data/data_histories.h" #include "data/data_peer.h" #include "data/data_session.h" +#include "dialogs/dialogs_entry.h" #include "history/history.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "menu/menu_mute.h" +#include "window/window_peer_menu.h" +#include "window/window_session_controller.h" namespace Dialogs { void PerformSwipeDialogAction( + not_null controller, not_null peer, - Ui::SwipeDialogAction action) { + Ui::SwipeDialogAction action, + FilterId filterId) { + const auto history = peer->owner().history(peer); if (action == Dialogs::Ui::SwipeDialogAction::Mute) { - const auto history = peer->owner().history(peer); - MuteMenu::ThreadDescriptor(history).updateMutePeriod( - std::numeric_limits::max()); + const auto isMuted = rpl::variable( + MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); + MuteMenu::ThreadDescriptor(history).updateMutePeriod(isMuted + ? 0 + : std::numeric_limits::max()); + } else if (action == Dialogs::Ui::SwipeDialogAction::Pin) { + const auto entry = (Dialogs::Entry*)(history); + Window::TogglePinnedThread(controller, entry, filterId); + } else if (action == Dialogs::Ui::SwipeDialogAction::Read) { + if (Window::IsUnreadThread(history)) { + Window::MarkAsReadThread(history); + } else if (history) { + peer->owner().histories().changeDialogUnreadMark(history, true); + } + } else if (action == Dialogs::Ui::SwipeDialogAction::Archive) { + history->session().api().toggleHistoryArchived( + history, + !Window::IsArchived(history), + [] {}); + } else if (action == Dialogs::Ui::SwipeDialogAction::Delete) { + Window::DeleteAndLeaveHandler(controller, peer)(); } } +QString ResolveSwipeDialogLottieIconName( + not_null peer, + Ui::SwipeDialogAction action, + FilterId filterId) { + if (action == Dialogs::Ui::SwipeDialogAction::Mute) { + const auto history = peer->owner().history(peer); + const auto isMuted = rpl::variable( + MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); + return isMuted ? u"swipe_unmute"_q : u"swipe_mute"_q; + } else if (action == Dialogs::Ui::SwipeDialogAction::Pin) { + const auto history = peer->owner().history(peer); + const auto entry = (Dialogs::Entry*)(history); + return entry->isPinnedDialog(filterId) + ? u"swipe_unpin"_q + : u"swipe_pin"_q; + } else if (action == Dialogs::Ui::SwipeDialogAction::Read) { + const auto history = peer->owner().history(peer); + return Window::IsUnreadThread(history) + ? u"swipe_read"_q + : u"swipe_unread"_q; + } else if (action == Dialogs::Ui::SwipeDialogAction::Archive) { + const auto history = peer->owner().history(peer); + return Window::IsArchived(history) + ? u"swipe_unarchive"_q + : u"swipe_archive"_q; + } else if (action == Dialogs::Ui::SwipeDialogAction::Delete) { + return u"swipe_delete"_q; + } + return u"swipe_disabled"_q; +} + +Ui::SwipeDialogActionLabel ResolveSwipeDialogLabel( + not_null history, + Ui::SwipeDialogAction action, + FilterId filterId) { + if (action == Dialogs::Ui::SwipeDialogAction::Mute) { + const auto isMuted = rpl::variable( + MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); + return isMuted + ? Ui::SwipeDialogActionLabel::Unmute + : Ui::SwipeDialogActionLabel::Mute; + } else if (action == Dialogs::Ui::SwipeDialogAction::Pin) { + const auto entry = (Dialogs::Entry*)(history); + return entry->isPinnedDialog(filterId) + ? Ui::SwipeDialogActionLabel::Unpin + : Ui::SwipeDialogActionLabel::Pin; + } else if (action == Dialogs::Ui::SwipeDialogAction::Read) { + return Window::IsUnreadThread(history) + ? Ui::SwipeDialogActionLabel::Read + : Ui::SwipeDialogActionLabel::Unread; + } else if (action == Dialogs::Ui::SwipeDialogAction::Archive) { + return Window::IsArchived(history) + ? Ui::SwipeDialogActionLabel::Unarchive + : Ui::SwipeDialogActionLabel::Archive; + } else if (action == Dialogs::Ui::SwipeDialogAction::Delete) { + return Ui::SwipeDialogActionLabel::Delete; + } + return Ui::SwipeDialogActionLabel::Disabled; +} + +QString ResolveSwipeDialogLabel(Ui::SwipeDialogActionLabel action) { + switch (action) { + case Ui::SwipeDialogActionLabel::Mute: + return tr::lng_settings_swipe_mute(tr::now); + case Ui::SwipeDialogActionLabel::Unmute: + return tr::lng_settings_swipe_unmute(tr::now); + case Ui::SwipeDialogActionLabel::Pin: + return tr::lng_settings_swipe_pin(tr::now); + case Ui::SwipeDialogActionLabel::Unpin: + return tr::lng_settings_swipe_unpin(tr::now); + case Ui::SwipeDialogActionLabel::Read: + return tr::lng_settings_swipe_read(tr::now); + case Ui::SwipeDialogActionLabel::Unread: + return tr::lng_settings_swipe_unread(tr::now); + case Ui::SwipeDialogActionLabel::Archive: + return tr::lng_settings_swipe_archive(tr::now); + case Ui::SwipeDialogActionLabel::Unarchive: + return tr::lng_settings_swipe_unarchive(tr::now); + case Ui::SwipeDialogActionLabel::Delete: + return tr::lng_settings_swipe_delete(tr::now); + default: + return tr::lng_settings_swipe_disabled(tr::now); + }; +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h index 9dca99277c..3918f983a7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h +++ b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h @@ -7,16 +7,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +class History; class PeerData; namespace Dialogs::Ui { enum class SwipeDialogAction; +enum class SwipeDialogActionLabel; } // namespace Dialogs::Ui +namespace Window { +class SessionController; +} // namespace Window + namespace Dialogs { void PerformSwipeDialogAction( + not_null controller, not_null peer, - Ui::SwipeDialogAction action); + Ui::SwipeDialogAction action, + FilterId filterId); + +[[nodiscard]] QString ResolveSwipeDialogLottieIconName( + not_null peer, + Ui::SwipeDialogAction action, + FilterId filterId); + +[[nodiscard]] Ui::SwipeDialogActionLabel ResolveSwipeDialogLabel( + not_null history, + Ui::SwipeDialogAction action, + FilterId filterId); + +[[nodiscard]] QString ResolveSwipeDialogLabel(Ui::SwipeDialogActionLabel); } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index c143945410..793a10b92f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -690,10 +690,11 @@ void Widget::setupSwipeBack() { Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=]( Ui::Controls::SwipeContextData data) { if (data.translation != 0) { - if (data.translation < 0) { - if (_inner) { - _inner->setSwipeContextData(std::move(data)); - } + if (data.translation < 0 + && _inner + && (Core::App().settings().swipeDialogAction() + != Ui::SwipeDialogAction::Disabled)) { + _inner->setSwipeContextData(std::move(data)); } else { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -726,16 +727,21 @@ void Widget::setupSwipeBack() { return Ui::Controls::SwipeHandlerFinishData(); } const auto isRightToLeft = direction == Qt::RightToLeft; + const auto action = Core::App().settings().swipeDialogAction(); + const auto isDisabled = action == Ui::SwipeDialogAction::Disabled; if (!isRightToLeft && _inner) { - if (const auto key = _inner->calcSwipeKey(top)) { - const auto action - = Core::App().settings().swipeDialogAction(); + if (const auto key = _inner->calcSwipeKey(top); + key && !isDisabled) { _inner->prepareSwipeAction(key, action); return Ui::Controls::SwipeHandlerFinishData{ .callback = [=, session = &session()] { auto callback = [=, peerId = PeerId(key)] { const auto peer = session->data().peer(peerId); - PerformSwipeDialogAction(peer, action); + PerformSwipeDialogAction( + controller(), + peer, + action, + _inner->filterId()); }; base::call_delayed( st::slideWrapDuration, @@ -788,7 +794,9 @@ void Widget::setupSwipeBack() { } }); } - if (_chatFilters && session().data().chatsFilters().has()) { + if (_chatFilters + && session().data().chatsFilters().has() + && isDisabled) { _swipeBackMirrored = !isRightToLeft; using namespace Window; const auto next = !isRightToLeft; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 9cb8692927..83b140292d 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "dialogs/dialogs_list.h" #include "dialogs/dialogs_three_state_icon.h" +#include "dialogs/dialogs_swipe_action.h" #include "dialogs/ui/dialogs_video_userpic.h" #include "history/history.h" #include "history/history_item.h" @@ -69,23 +70,11 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_"; return !history->isForum(); } -[[nodiscard]] QString SwipeActionText(Dialogs::Ui::SwipeDialogAction type) { - return (type == Dialogs::Ui::SwipeDialogAction::Archive) - ? tr::lng_settings_swipe_archive(tr::now) - : (type == Dialogs::Ui::SwipeDialogAction::Delete) - ? tr::lng_settings_swipe_delete(tr::now) - : (type == Dialogs::Ui::SwipeDialogAction::Read) - ? tr::lng_settings_swipe_read(tr::now) - : (type == Dialogs::Ui::SwipeDialogAction::Pin) - ? tr::lng_settings_swipe_pin(tr::now) - : tr::lng_settings_swipe_mute(tr::now); -} - const style::font &SwipeActionFont( - Dialogs::Ui::SwipeDialogAction action, + Dialogs::Ui::SwipeDialogActionLabel action, int availableWidth) { struct Entry final { - Dialogs::Ui::SwipeDialogAction action; + Dialogs::Ui::SwipeDialogActionLabel action; QString langId; style::font font; }; @@ -104,7 +93,7 @@ const style::font &SwipeActionFont( style::ConvertScale(i, style::Scale()), st::semiboldFont->flags(), st::semiboldFont->family()); - if (font->width(SwipeActionText(action)) <= availableWidth + if (font->width(ResolveSwipeDialogLabel(action)) <= availableWidth || i == kMinFontSize) { Fonts.emplace_back(Entry{ .action = action, @@ -920,11 +909,14 @@ void PaintRow( - iconOffset * 2 - st::dialogsSwipeActionSize; const auto availableWidth = geometry.width() - left; - p.setFont( - SwipeActionFont(context.swipeContext.action, availableWidth)); + const auto labelType = ResolveSwipeDialogLabel( + history, + context.swipeContext.action, + context.filter); + p.setFont(SwipeActionFont(labelType, availableWidth)); p.drawText( QRect(left, 0, availableWidth, geometry.height()), - SwipeActionText(context.swipeContext.action), + ResolveSwipeDialogLabel(labelType), style::al_bottom); } p.translate(0, topTranslation); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h index bbfc81d018..03cfdadb64 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h @@ -26,6 +26,19 @@ enum class SwipeDialogAction { Disabled, }; +enum class SwipeDialogActionLabel { + Mute, + Unmute, + Pin, + Unpin, + Read, + Unread, + Archive, + Unarchive, + Delete, + Disabled, +}; + struct SwipeContext { ::Ui::Controls::SwipeContextData data; Lottie::Icon *icon = nullptr; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index b4c1daf624..9768688eed 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -438,40 +438,6 @@ void TogglePinnedThread( } } -void TogglePinnedThread( - not_null controller, - not_null entry, - FilterId filterId) { - if (!filterId) { - return TogglePinnedThread(controller, entry); - } - const auto history = entry->asHistory(); - if (!history) { - return; - } - const auto owner = &history->owner(); - - // This can happen when you remove this filter from another client. - if (!ranges::contains( - (&owner->session())->data().chatsFilters().list(), - filterId, - &Data::ChatFilter::id)) { - controller->showToast(tr::lng_cant_do_this(tr::now)); - return; - } - - const auto isPinned = !history->isPinnedDialog(filterId); - if (isPinned && PinnedLimitReached(controller, history, filterId)) { - return; - } - - owner->setChatPinned(history, filterId, isPinned); - Api::SaveNewFilterPinned(&owner->session(), filterId); - if (isPinned) { - controller->content()->dialogsToUp(); - } -} - Filler::Filler( not_null controller, Dialogs::EntryState request, @@ -726,7 +692,7 @@ void Filler::addToggleArchive() { } } const auto isArchived = [=] { - return (history->folder() != nullptr); + return IsArchived(history); }; const auto label = [=] { return isArchived() @@ -3316,4 +3282,42 @@ void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction) { addAction({ .addTopShift = -shift }); } +void TogglePinnedThread( + not_null controller, + not_null entry, + FilterId filterId) { + if (!filterId) { + return TogglePinnedThread(controller, entry); + } + const auto history = entry->asHistory(); + if (!history) { + return; + } + const auto owner = &history->owner(); + + // This can happen when you remove this filter from another client. + if (!ranges::contains( + (&owner->session())->data().chatsFilters().list(), + filterId, + &Data::ChatFilter::id)) { + controller->showToast(tr::lng_cant_do_this(tr::now)); + return; + } + + const auto isPinned = !history->isPinnedDialog(filterId); + if (isPinned && PinnedLimitReached(controller, history, filterId)) { + return; + } + + owner->setChatPinned(history, filterId, isPinned); + Api::SaveNewFilterPinned(&owner->session(), filterId); + if (isPinned) { + controller->content()->dialogsToUp(); + } +} + +bool IsArchived(not_null history) { + return (history->folder() != nullptr); +} + } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 97268366c7..e3adf079bf 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -40,6 +40,7 @@ class MainList; struct EntryState; struct UnreadState; class Key; +class Entry; } // namespace Dialogs namespace ChatHelpers { @@ -196,6 +197,10 @@ void ToggleMessagePinned( not_null navigation, FullMsgId itemId, bool pin); +void TogglePinnedThread( + not_null controller, + not_null entry, + FilterId filterId); void HidePinnedBar( not_null navigation, not_null peer, @@ -210,4 +215,6 @@ void MarkAsReadThread(not_null thread); void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction); +[[nodiscard]] bool IsArchived(not_null history); + } // namespace Window From b079c79390becd9e4554c809c66dcf1a081c935f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Mar 2025 10:02:36 +0300 Subject: [PATCH 063/122] Added correspond colors for every swipe dialog action. --- .../dialogs/dialogs_swipe_action.cpp | 25 +++++++++++++++++++ .../dialogs/dialogs_swipe_action.h | 5 ++++ .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 12 ++++----- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp index 9409b3a1e5..4a780ad956 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp @@ -138,4 +138,29 @@ QString ResolveSwipeDialogLabel(Ui::SwipeDialogActionLabel action) { }; } +const style::color &ResolveSwipeActionBg( + Ui::SwipeDialogActionLabel action) { + switch (action) { + case Ui::SwipeDialogActionLabel::Delete: + return st::attentionButtonFg; + case Ui::SwipeDialogActionLabel::Disabled: + return st::windowSubTextFgOver; + case Ui::SwipeDialogActionLabel::Mute: + case Ui::SwipeDialogActionLabel::Unmute: + case Ui::SwipeDialogActionLabel::Pin: + case Ui::SwipeDialogActionLabel::Unpin: + case Ui::SwipeDialogActionLabel::Read: + case Ui::SwipeDialogActionLabel::Unread: + case Ui::SwipeDialogActionLabel::Archive: + case Ui::SwipeDialogActionLabel::Unarchive: + default: + return st::windowBgActive; + }; +} + +const style::color &ResolveSwipeActionBgActive( + Ui::SwipeDialogActionLabel action) { + return st::windowSubTextFgOver; +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h index 3918f983a7..db49ce31d5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h +++ b/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h @@ -39,4 +39,9 @@ void PerformSwipeDialogAction( [[nodiscard]] QString ResolveSwipeDialogLabel(Ui::SwipeDialogActionLabel); +[[nodiscard]] const style::color &ResolveSwipeActionBg( + Ui::SwipeDialogActionLabel); +[[nodiscard]] const style::color &ResolveSwipeActionBgActive( + Ui::SwipeDialogActionLabel); + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 83b140292d..c22bc2dc40 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -879,10 +879,14 @@ void PaintRow( swipeTranslation, geometry.height()); p.setClipRegion(swipeActionRect); - p.fillRect(swipeActionRect, st::attentionButtonFg); + const auto labelType = ResolveSwipeDialogLabel( + history, + context.swipeContext.action, + context.filter); + p.fillRect(swipeActionRect, ResolveSwipeActionBg(labelType)); if (context.swipeContext.data.reachRatio) { p.setPen(Qt::NoPen); - p.setBrush(st::windowBgActive); + p.setBrush(ResolveSwipeActionBgActive(labelType)); const auto r = swipeTranslation * context.swipeContext.data.reachRatio; const auto offset = st::dialogsSwipeActionSize @@ -909,10 +913,6 @@ void PaintRow( - iconOffset * 2 - st::dialogsSwipeActionSize; const auto availableWidth = geometry.width() - left; - const auto labelType = ResolveSwipeDialogLabel( - history, - context.swipeContext.action, - context.filter); p.setFont(SwipeActionFont(labelType, availableWidth)); p.drawText( QRect(left, 0, availableWidth, geometry.height()), From 25ed8fe04400968e8a64ffa76326439ae91314fe Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Mar 2025 09:26:55 +0300 Subject: [PATCH 064/122] Renamed swipe dialog action to quick dialog action. --- Telegram/CMakeLists.txt | 4 +- Telegram/SourceFiles/core/core_settings.cpp | 18 +-- Telegram/SourceFiles/core/core_settings.h | 10 +- Telegram/SourceFiles/dialogs/dialogs.style | 2 +- .../dialogs/dialogs_inner_widget.cpp | 49 ++++---- .../dialogs/dialogs_inner_widget.h | 10 +- ...pe_action.cpp => dialogs_quick_action.cpp} | 114 +++++++++--------- ..._swipe_action.h => dialogs_quick_action.h} | 26 ++-- .../SourceFiles/dialogs/dialogs_widget.cpp | 14 +-- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 44 +++---- .../SourceFiles/dialogs/ui/dialogs_layout.h | 4 +- ...ntext.h => dialogs_quick_action_context.h} | 8 +- .../SourceFiles/settings/settings_chat.cpp | 14 +-- Telegram/cmake/td_ui.cmake | 2 +- 14 files changed, 161 insertions(+), 158 deletions(-) rename Telegram/SourceFiles/dialogs/{dialogs_swipe_action.cpp => dialogs_quick_action.cpp} (55%) rename Telegram/SourceFiles/dialogs/{dialogs_swipe_action.h => dialogs_quick_action.h} (54%) rename Telegram/SourceFiles/dialogs/ui/{dialogs_swipe_context.h => dialogs_quick_action_context.h} (84%) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 24b7ed6722..2e669d6324 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -682,14 +682,14 @@ PRIVATE dialogs/dialogs_main_list.h dialogs/dialogs_pinned_list.cpp dialogs/dialogs_pinned_list.h + dialogs/dialogs_quick_action.cpp + dialogs/dialogs_quick_action.h dialogs/dialogs_row.cpp dialogs/dialogs_row.h dialogs/dialogs_search_from_controllers.cpp dialogs/dialogs_search_from_controllers.h dialogs/dialogs_search_tags.cpp dialogs/dialogs_search_tags.h - dialogs/dialogs_swipe_action.cpp - dialogs/dialogs_swipe_action.h dialogs/dialogs_widget.cpp dialogs/dialogs_widget.h editor/color_picker.cpp diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 08e04273d8..26a88cef8c 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -402,7 +402,7 @@ QByteArray Settings::serialize() const { << SerializeVideoQuality(_videoQuality) << qint32(_ivZoom.current()) << qint32(_systemDarkModeEnabled.current() ? 1 : 0) - << qint32(_swipeDialogAction); + << qint32(_quickDialogAction); } Ensures(result.size() == size); @@ -531,7 +531,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0; quint32 videoQuality = SerializeVideoQuality(_videoQuality); quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0; - quint32 swipeDialogAction = quint32(_swipeDialogAction); + quint32 quickDialogAction = quint32(_quickDialogAction); stream >> themesAccentColors; if (!stream.atEnd()) { @@ -861,7 +861,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { stream >> systemDarkModeEnabled; } if (!stream.atEnd()) { - stream >> swipeDialogAction; + stream >> quickDialogAction; } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " @@ -1084,7 +1084,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _recordVideoMessages = (recordVideoMessages == 1); _videoQuality = DeserializeVideoQuality(videoQuality); _chatFiltersHorizontal = (chatFiltersHorizontal == 1); - _swipeDialogAction = Dialogs::Ui::SwipeDialogAction(swipeDialogAction); + _quickDialogAction = Dialogs::Ui::QuickDialogAction(quickDialogAction); } QString Settings::getSoundPath(const QString &key) const { @@ -1476,7 +1476,7 @@ void Settings::resetOnLastLogout() { _recordVideoMessages = false; _videoQuality = {}; _chatFiltersHorizontal = false; - _swipeDialogAction = Dialogs::Ui::SwipeDialogAction::Disabled; + _quickDialogAction = Dialogs::Ui::QuickDialogAction::Disabled; _recentEmojiPreload.clear(); _recentEmoji.clear(); @@ -1664,12 +1664,12 @@ void Settings::setChatFiltersHorizontal(bool value) { _chatFiltersHorizontal = value; } -Dialogs::Ui::SwipeDialogAction Settings::swipeDialogAction() const { - return _swipeDialogAction; +Dialogs::Ui::QuickDialogAction Settings::quickDialogAction() const { + return _quickDialogAction; } -void Settings::setSwipeDialogAction(Dialogs::Ui::SwipeDialogAction action) { - _swipeDialogAction = action; +void Settings::setQuickDialogAction(Dialogs::Ui::QuickDialogAction action) { + _quickDialogAction = action; } } // namespace Core diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 9492037fa2..bcfa4d05b9 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings_proxy.h" #include "media/media_common.h" -#include "dialogs/ui/dialogs_swipe_context.h" +#include "dialogs/ui/dialogs_quick_action_context.h" #include "window/themes/window_themes_embedded.h" #include "ui/chat/attach/attach_send_files_way.h" #include "base/flags.h" @@ -947,8 +947,8 @@ public: [[nodiscard]] static PlaybackSpeed DeserializePlaybackSpeed( qint32 speed); - [[nodiscard]] Dialogs::Ui::SwipeDialogAction swipeDialogAction() const; - void setSwipeDialogAction(Dialogs::Ui::SwipeDialogAction); + [[nodiscard]] Dialogs::Ui::QuickDialogAction quickDialogAction() const; + void setQuickDialogAction(Dialogs::Ui::QuickDialogAction); void resetOnLastLogout(); @@ -1090,8 +1090,8 @@ private: bool _recordVideoMessages = false; - Dialogs::Ui::SwipeDialogAction _swipeDialogAction - = Dialogs::Ui::SwipeDialogAction::Disabled; + Dialogs::Ui::QuickDialogAction _quickDialogAction + = Dialogs::Ui::QuickDialogAction::Disabled; QByteArray _photoEditorBrush; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 2d4d01fda5..d537bfeada 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -790,4 +790,4 @@ dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) { minWidth: 128px; } -dialogsSwipeActionSize: 20px; +dialogsQuickActionSize: 20px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3cd6952ce9..47791b964b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_search_from_controllers.h" #include "dialogs/dialogs_search_tags.h" -#include "dialogs/dialogs_swipe_action.h" +#include "dialogs/dialogs_quick_action.h" #include "history/view/history_view_context_menu.h" #include "history/history.h" #include "history/history_item.h" @@ -810,7 +810,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto ms = crl::now(); const auto childListShown = _childListShown.current(); auto context = Ui::PaintContext{ - .swipeContext = _swipeContext, + .quickActionContext = _quickActionContext, .st = _st, .topicJumpCache = _topicJumpCache.get(), .folder = _openedFolder, @@ -4996,22 +4996,23 @@ rpl::producer InnerWidget::openBotMainAppRequests() const { } void InnerWidget::setSwipeContextData(Ui::Controls::SwipeContextData data) { - _swipeContext.data = std::move(data); - if (_swipeContext.data.msgBareId) { + _quickActionContext.data = std::move(data); + if (_quickActionContext.data.msgBareId) { constexpr auto kStartAnimateThreshold = 0.32; constexpr auto kResetAnimateThreshold = 0.24; - if (_swipeContext.data.ratio > kStartAnimateThreshold) { - if (_swipeContext.icon - && !_swipeContext.icon->frameIndex() - && !_swipeContext.icon->animating()) { - _swipeContext.icon->animate( + if (_quickActionContext.data.ratio > kStartAnimateThreshold) { + if (_quickActionContext.icon + && !_quickActionContext.icon->frameIndex() + && !_quickActionContext.icon->animating()) { + _quickActionContext.icon->animate( [=] { update(); }, 0, - _swipeContext.icon->framesCount()); + _quickActionContext.icon->framesCount()); } - } else if (_swipeContext.data.ratio < kResetAnimateThreshold) { - if (_swipeContext.icon && _swipeContext.icon->frameIndex()) { - _swipeContext.icon->jumpTo(0, [=] { update(); }); + } else if (_quickActionContext.data.ratio < kResetAnimateThreshold) { + if (_quickActionContext.icon + && _quickActionContext.icon->frameIndex()) { + _quickActionContext.icon->jumpTo(0, [=] { update(); }); } } update(); @@ -5034,24 +5035,24 @@ int64 InnerWidget::calcSwipeKey(int top) { return 0; } -void InnerWidget::prepareSwipeAction( +void InnerWidget::prepareQuickAction( int64 key, - Dialogs::Ui::SwipeDialogAction action) { + Dialogs::Ui::QuickDialogAction action) { if (key) { const auto peer = session().data().peer(PeerId(key)); - auto name = ResolveSwipeDialogLottieIconName(peer, action, _filterId); - _swipeLottieIcon = Lottie::MakeIcon({ + auto name = ResolveQuickDialogLottieIconName(peer, action, _filterId); + _quickActionLottieIcon = Lottie::MakeIcon({ .name = std::move(name), - .sizeOverride = Size(st::dialogsSwipeActionSize), + .sizeOverride = Size(st::dialogsQuickActionSize), }); - _swipeContext.icon = _swipeLottieIcon.get(); - _swipeContext.action = action; + _quickActionContext.icon = _quickActionLottieIcon.get(); + _quickActionContext.action = action; } else { - if (_swipeContext.icon) { - _swipeContext = {}; + if (_quickActionContext.icon) { + _quickActionContext = {}; } - if (_swipeLottieIcon) { - _swipeLottieIcon.reset(); + if (_quickActionLottieIcon) { + _quickActionLottieIcon.reset(); } } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 846feac821..8b239d4440 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" #include "base/timer.h" #include "dialogs/dialogs_key.h" -#include "dialogs/ui/dialogs_swipe_context.h" +#include "dialogs/ui/dialogs_quick_action_context.h" #include "data/data_messages.h" #include "ui/dragging_scroll_manager.h" #include "ui/effects/animations.h" @@ -40,7 +40,7 @@ class PopupMenu; class FlatLabel; struct ScrollToRequest; namespace Controls { -enum class SwipeDialogAction; +enum class QuickDialogAction; } // namespace Controls } // namespace Ui @@ -218,7 +218,7 @@ public: void setSwipeContextData(Ui::Controls::SwipeContextData data); [[nodiscard]] int64 calcSwipeKey(int top); - void prepareSwipeAction(int64 key, Dialogs::Ui::SwipeDialogAction); + void prepareQuickAction(int64 key, Dialogs::Ui::QuickDialogAction); protected: void visibleTopBottomUpdated( @@ -623,8 +623,8 @@ private: rpl::event_stream<> _refreshHashtagsRequests; rpl::event_stream _openBotMainAppRequests; - Dialogs::Ui::SwipeContext _swipeContext; - std::unique_ptr _swipeLottieIcon = nullptr; + Dialogs::Ui::QuickActionContext _quickActionContext; + std::unique_ptr _quickActionLottieIcon = nullptr; RowDescriptor _chatPreviewRow; bool _chatPreviewScheduled = false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp similarity index 55% rename from Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp rename to Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index 4a780ad956..8bafb68690 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -5,9 +5,9 @@ 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 "dialogs/dialogs_swipe_action.h" +#include "dialogs/dialogs_quick_action.h" -#include "dialogs/ui/dialogs_swipe_context.h" +#include "dialogs/ui/dialogs_quick_action_context.h" #include "apiwrap.h" #include "data/data_histories.h" #include "data/data_peer.h" @@ -22,144 +22,144 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs { -void PerformSwipeDialogAction( +void PerformQuickDialogAction( not_null controller, not_null peer, - Ui::SwipeDialogAction action, + Ui::QuickDialogAction action, FilterId filterId) { const auto history = peer->owner().history(peer); - if (action == Dialogs::Ui::SwipeDialogAction::Mute) { + if (action == Dialogs::Ui::QuickDialogAction::Mute) { const auto isMuted = rpl::variable( MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); MuteMenu::ThreadDescriptor(history).updateMutePeriod(isMuted ? 0 : std::numeric_limits::max()); - } else if (action == Dialogs::Ui::SwipeDialogAction::Pin) { + } else if (action == Dialogs::Ui::QuickDialogAction::Pin) { const auto entry = (Dialogs::Entry*)(history); Window::TogglePinnedThread(controller, entry, filterId); - } else if (action == Dialogs::Ui::SwipeDialogAction::Read) { + } else if (action == Dialogs::Ui::QuickDialogAction::Read) { if (Window::IsUnreadThread(history)) { Window::MarkAsReadThread(history); } else if (history) { peer->owner().histories().changeDialogUnreadMark(history, true); } - } else if (action == Dialogs::Ui::SwipeDialogAction::Archive) { + } else if (action == Dialogs::Ui::QuickDialogAction::Archive) { history->session().api().toggleHistoryArchived( history, !Window::IsArchived(history), [] {}); - } else if (action == Dialogs::Ui::SwipeDialogAction::Delete) { + } else if (action == Dialogs::Ui::QuickDialogAction::Delete) { Window::DeleteAndLeaveHandler(controller, peer)(); } } -QString ResolveSwipeDialogLottieIconName( +QString ResolveQuickDialogLottieIconName( not_null peer, - Ui::SwipeDialogAction action, + Ui::QuickDialogAction action, FilterId filterId) { - if (action == Dialogs::Ui::SwipeDialogAction::Mute) { + if (action == Dialogs::Ui::QuickDialogAction::Mute) { const auto history = peer->owner().history(peer); const auto isMuted = rpl::variable( MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); return isMuted ? u"swipe_unmute"_q : u"swipe_mute"_q; - } else if (action == Dialogs::Ui::SwipeDialogAction::Pin) { + } else if (action == Dialogs::Ui::QuickDialogAction::Pin) { const auto history = peer->owner().history(peer); const auto entry = (Dialogs::Entry*)(history); return entry->isPinnedDialog(filterId) ? u"swipe_unpin"_q : u"swipe_pin"_q; - } else if (action == Dialogs::Ui::SwipeDialogAction::Read) { + } else if (action == Dialogs::Ui::QuickDialogAction::Read) { const auto history = peer->owner().history(peer); return Window::IsUnreadThread(history) ? u"swipe_read"_q : u"swipe_unread"_q; - } else if (action == Dialogs::Ui::SwipeDialogAction::Archive) { + } else if (action == Dialogs::Ui::QuickDialogAction::Archive) { const auto history = peer->owner().history(peer); return Window::IsArchived(history) ? u"swipe_unarchive"_q : u"swipe_archive"_q; - } else if (action == Dialogs::Ui::SwipeDialogAction::Delete) { + } else if (action == Dialogs::Ui::QuickDialogAction::Delete) { return u"swipe_delete"_q; } return u"swipe_disabled"_q; } -Ui::SwipeDialogActionLabel ResolveSwipeDialogLabel( +Ui::QuickDialogActionLabel ResolveQuickDialogLabel( not_null history, - Ui::SwipeDialogAction action, + Ui::QuickDialogAction action, FilterId filterId) { - if (action == Dialogs::Ui::SwipeDialogAction::Mute) { + if (action == Dialogs::Ui::QuickDialogAction::Mute) { const auto isMuted = rpl::variable( MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); return isMuted - ? Ui::SwipeDialogActionLabel::Unmute - : Ui::SwipeDialogActionLabel::Mute; - } else if (action == Dialogs::Ui::SwipeDialogAction::Pin) { + ? Ui::QuickDialogActionLabel::Unmute + : Ui::QuickDialogActionLabel::Mute; + } else if (action == Dialogs::Ui::QuickDialogAction::Pin) { const auto entry = (Dialogs::Entry*)(history); return entry->isPinnedDialog(filterId) - ? Ui::SwipeDialogActionLabel::Unpin - : Ui::SwipeDialogActionLabel::Pin; - } else if (action == Dialogs::Ui::SwipeDialogAction::Read) { + ? Ui::QuickDialogActionLabel::Unpin + : Ui::QuickDialogActionLabel::Pin; + } else if (action == Dialogs::Ui::QuickDialogAction::Read) { return Window::IsUnreadThread(history) - ? Ui::SwipeDialogActionLabel::Read - : Ui::SwipeDialogActionLabel::Unread; - } else if (action == Dialogs::Ui::SwipeDialogAction::Archive) { + ? Ui::QuickDialogActionLabel::Read + : Ui::QuickDialogActionLabel::Unread; + } else if (action == Dialogs::Ui::QuickDialogAction::Archive) { return Window::IsArchived(history) - ? Ui::SwipeDialogActionLabel::Unarchive - : Ui::SwipeDialogActionLabel::Archive; - } else if (action == Dialogs::Ui::SwipeDialogAction::Delete) { - return Ui::SwipeDialogActionLabel::Delete; + ? Ui::QuickDialogActionLabel::Unarchive + : Ui::QuickDialogActionLabel::Archive; + } else if (action == Dialogs::Ui::QuickDialogAction::Delete) { + return Ui::QuickDialogActionLabel::Delete; } - return Ui::SwipeDialogActionLabel::Disabled; + return Ui::QuickDialogActionLabel::Disabled; } -QString ResolveSwipeDialogLabel(Ui::SwipeDialogActionLabel action) { +QString ResolveQuickDialogLabel(Ui::QuickDialogActionLabel action) { switch (action) { - case Ui::SwipeDialogActionLabel::Mute: + case Ui::QuickDialogActionLabel::Mute: return tr::lng_settings_swipe_mute(tr::now); - case Ui::SwipeDialogActionLabel::Unmute: + case Ui::QuickDialogActionLabel::Unmute: return tr::lng_settings_swipe_unmute(tr::now); - case Ui::SwipeDialogActionLabel::Pin: + case Ui::QuickDialogActionLabel::Pin: return tr::lng_settings_swipe_pin(tr::now); - case Ui::SwipeDialogActionLabel::Unpin: + case Ui::QuickDialogActionLabel::Unpin: return tr::lng_settings_swipe_unpin(tr::now); - case Ui::SwipeDialogActionLabel::Read: + case Ui::QuickDialogActionLabel::Read: return tr::lng_settings_swipe_read(tr::now); - case Ui::SwipeDialogActionLabel::Unread: + case Ui::QuickDialogActionLabel::Unread: return tr::lng_settings_swipe_unread(tr::now); - case Ui::SwipeDialogActionLabel::Archive: + case Ui::QuickDialogActionLabel::Archive: return tr::lng_settings_swipe_archive(tr::now); - case Ui::SwipeDialogActionLabel::Unarchive: + case Ui::QuickDialogActionLabel::Unarchive: return tr::lng_settings_swipe_unarchive(tr::now); - case Ui::SwipeDialogActionLabel::Delete: + case Ui::QuickDialogActionLabel::Delete: return tr::lng_settings_swipe_delete(tr::now); default: return tr::lng_settings_swipe_disabled(tr::now); }; } -const style::color &ResolveSwipeActionBg( - Ui::SwipeDialogActionLabel action) { +const style::color &ResolveQuickActionBg( + Ui::QuickDialogActionLabel action) { switch (action) { - case Ui::SwipeDialogActionLabel::Delete: + case Ui::QuickDialogActionLabel::Delete: return st::attentionButtonFg; - case Ui::SwipeDialogActionLabel::Disabled: + case Ui::QuickDialogActionLabel::Disabled: return st::windowSubTextFgOver; - case Ui::SwipeDialogActionLabel::Mute: - case Ui::SwipeDialogActionLabel::Unmute: - case Ui::SwipeDialogActionLabel::Pin: - case Ui::SwipeDialogActionLabel::Unpin: - case Ui::SwipeDialogActionLabel::Read: - case Ui::SwipeDialogActionLabel::Unread: - case Ui::SwipeDialogActionLabel::Archive: - case Ui::SwipeDialogActionLabel::Unarchive: + case Ui::QuickDialogActionLabel::Mute: + case Ui::QuickDialogActionLabel::Unmute: + case Ui::QuickDialogActionLabel::Pin: + case Ui::QuickDialogActionLabel::Unpin: + case Ui::QuickDialogActionLabel::Read: + case Ui::QuickDialogActionLabel::Unread: + case Ui::QuickDialogActionLabel::Archive: + case Ui::QuickDialogActionLabel::Unarchive: default: return st::windowBgActive; }; } -const style::color &ResolveSwipeActionBgActive( - Ui::SwipeDialogActionLabel action) { +const style::color &ResolveQuickActionBgActive( + Ui::QuickDialogActionLabel action) { return st::windowSubTextFgOver; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h similarity index 54% rename from Telegram/SourceFiles/dialogs/dialogs_swipe_action.h rename to Telegram/SourceFiles/dialogs/dialogs_quick_action.h index db49ce31d5..e425cd08dc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_swipe_action.h +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h @@ -11,8 +11,8 @@ class History; class PeerData; namespace Dialogs::Ui { -enum class SwipeDialogAction; -enum class SwipeDialogActionLabel; +enum class QuickDialogAction; +enum class QuickDialogActionLabel; } // namespace Dialogs::Ui namespace Window { @@ -21,27 +21,27 @@ class SessionController; namespace Dialogs { -void PerformSwipeDialogAction( +void PerformQuickDialogAction( not_null controller, not_null peer, - Ui::SwipeDialogAction action, + Ui::QuickDialogAction action, FilterId filterId); -[[nodiscard]] QString ResolveSwipeDialogLottieIconName( +[[nodiscard]] QString ResolveQuickDialogLottieIconName( not_null peer, - Ui::SwipeDialogAction action, + Ui::QuickDialogAction action, FilterId filterId); -[[nodiscard]] Ui::SwipeDialogActionLabel ResolveSwipeDialogLabel( +[[nodiscard]] Ui::QuickDialogActionLabel ResolveQuickDialogLabel( not_null history, - Ui::SwipeDialogAction action, + Ui::QuickDialogAction action, FilterId filterId); -[[nodiscard]] QString ResolveSwipeDialogLabel(Ui::SwipeDialogActionLabel); +[[nodiscard]] QString ResolveQuickDialogLabel(Ui::QuickDialogActionLabel); -[[nodiscard]] const style::color &ResolveSwipeActionBg( - Ui::SwipeDialogActionLabel); -[[nodiscard]] const style::color &ResolveSwipeActionBgActive( - Ui::SwipeDialogActionLabel); +[[nodiscard]] const style::color &ResolveQuickActionBg( + Ui::QuickDialogActionLabel); +[[nodiscard]] const style::color &ResolveQuickActionBgActive( + Ui::QuickDialogActionLabel); } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 793a10b92f..ab58ceed76 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_suggestions.h" #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_search_from_controllers.h" -#include "dialogs/dialogs_swipe_action.h" +#include "dialogs/dialogs_quick_action.h" #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_item.h" @@ -692,8 +692,8 @@ void Widget::setupSwipeBack() { if (data.translation != 0) { if (data.translation < 0 && _inner - && (Core::App().settings().swipeDialogAction() - != Ui::SwipeDialogAction::Disabled)) { + && (Core::App().settings().quickDialogAction() + != Ui::QuickDialogAction::Disabled)) { _inner->setSwipeContextData(std::move(data)); } else { if (!_swipeBackData.callback) { @@ -727,17 +727,17 @@ void Widget::setupSwipeBack() { return Ui::Controls::SwipeHandlerFinishData(); } const auto isRightToLeft = direction == Qt::RightToLeft; - const auto action = Core::App().settings().swipeDialogAction(); - const auto isDisabled = action == Ui::SwipeDialogAction::Disabled; + const auto action = Core::App().settings().quickDialogAction(); + const auto isDisabled = action == Ui::QuickDialogAction::Disabled; if (!isRightToLeft && _inner) { if (const auto key = _inner->calcSwipeKey(top); key && !isDisabled) { - _inner->prepareSwipeAction(key, action); + _inner->prepareQuickAction(key, action); return Ui::Controls::SwipeHandlerFinishData{ .callback = [=, session = &session()] { auto callback = [=, peerId = PeerId(key)] { const auto peer = session->data().peer(peerId); - PerformSwipeDialogAction( + PerformQuickDialogAction( controller(), peer, action, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index c22bc2dc40..8ab33852ef 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "dialogs/dialogs_list.h" #include "dialogs/dialogs_three_state_icon.h" -#include "dialogs/dialogs_swipe_action.h" +#include "dialogs/dialogs_quick_action.h" #include "dialogs/ui/dialogs_video_userpic.h" #include "history/history.h" #include "history/history_item.h" @@ -71,10 +71,10 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_"; } const style::font &SwipeActionFont( - Dialogs::Ui::SwipeDialogActionLabel action, + Dialogs::Ui::QuickDialogActionLabel action, int availableWidth) { struct Entry final { - Dialogs::Ui::SwipeDialogActionLabel action; + Dialogs::Ui::QuickDialogActionLabel action; QString langId; style::font font; }; @@ -93,7 +93,7 @@ const style::font &SwipeActionFont( style::ConvertScale(i, style::Scale()), st::semiboldFont->flags(), st::semiboldFont->family()); - if (font->width(ResolveSwipeDialogLabel(action)) <= availableWidth + if (font->width(ResolveQuickDialogLabel(action)) <= availableWidth || i == kMinFontSize) { Fonts.emplace_back(Entry{ .action = action, @@ -395,9 +395,11 @@ void PaintRow( : context.currentBg; auto swipeTranslation = 0; if (history - && history->peer->id.value == context.swipeContext.data.msgBareId) { - if (context.swipeContext.data.translation != 0) { - swipeTranslation = context.swipeContext.data.translation * -2; + && (history->peer->id.value + == context.quickActionContext.data.msgBareId)) { + if (context.quickActionContext.data.translation != 0) { + swipeTranslation = context.quickActionContext.data.translation + * -2; } } if (swipeTranslation) { @@ -879,30 +881,30 @@ void PaintRow( swipeTranslation, geometry.height()); p.setClipRegion(swipeActionRect); - const auto labelType = ResolveSwipeDialogLabel( + const auto labelType = ResolveQuickDialogLabel( history, - context.swipeContext.action, + context.quickActionContext.action, context.filter); - p.fillRect(swipeActionRect, ResolveSwipeActionBg(labelType)); - if (context.swipeContext.data.reachRatio) { + p.fillRect(swipeActionRect, ResolveQuickActionBg(labelType)); + if (context.quickActionContext.data.reachRatio) { p.setPen(Qt::NoPen); - p.setBrush(ResolveSwipeActionBgActive(labelType)); + p.setBrush(ResolveQuickActionBgActive(labelType)); const auto r = swipeTranslation - * context.swipeContext.data.reachRatio; - const auto offset = st::dialogsSwipeActionSize - + st::dialogsSwipeActionSize / 2.; + * context.quickActionContext.data.reachRatio; + const auto offset = st::dialogsQuickActionSize + + st::dialogsQuickActionSize / 2.; p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r); } const auto iconOffset = (geometry.height() - - st::dialogsSwipeActionSize) / 2; + - st::dialogsQuickActionSize) / 2; const auto topTranslation = iconOffset / 2.; p.translate(0, -topTranslation); - if (context.swipeContext.icon) { - context.swipeContext.icon->paint( + if (context.quickActionContext.icon) { + context.quickActionContext.icon->paint( p, rect::right(geometry) - iconOffset - - st::dialogsSwipeActionSize, + - st::dialogsQuickActionSize, iconOffset, st::premiumButtonFg->c); } @@ -911,12 +913,12 @@ void PaintRow( p.setBrush(Qt::NoBrush); const auto left = rect::right(geometry) - iconOffset * 2 - - st::dialogsSwipeActionSize; + - st::dialogsQuickActionSize; const auto availableWidth = geometry.width() - left; p.setFont(SwipeActionFont(labelType, availableWidth)); p.drawText( QRect(left, 0, availableWidth, geometry.height()), - ResolveSwipeDialogLabel(labelType), + ResolveQuickDialogLabel(labelType), style::al_bottom); } p.translate(0, topTranslation); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index 9b905eac82..848ac257f1 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "dialogs/ui/dialogs_swipe_context.h" +#include "dialogs/ui/dialogs_quick_action_context.h" #include "ui/cached_round_corners.h" namespace style { @@ -55,7 +55,7 @@ struct TopicJumpCache { struct PaintContext { RightButton *rightButton = nullptr; std::vector *chatsFilterTags = nullptr; - SwipeContext swipeContext; + QuickActionContext quickActionContext; not_null st; TopicJumpCache *topicJumpCache = nullptr; Data::Folder *folder = nullptr; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h similarity index 84% rename from Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h rename to Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h index 03cfdadb64..a8f5a5e9ff 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_swipe_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h @@ -17,7 +17,7 @@ namespace Dialogs::Ui { using namespace ::Ui; -enum class SwipeDialogAction { +enum class QuickDialogAction { Mute, Pin, Read, @@ -26,7 +26,7 @@ enum class SwipeDialogAction { Disabled, }; -enum class SwipeDialogActionLabel { +enum class QuickDialogActionLabel { Mute, Unmute, Pin, @@ -39,10 +39,10 @@ enum class SwipeDialogActionLabel { Disabled, }; -struct SwipeContext { +struct QuickActionContext { ::Ui::Controls::SwipeContextData data; Lottie::Icon *icon = nullptr; - SwipeDialogAction action; + QuickDialogAction action; }; } // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index b6e8f495e4..bf803cc512 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -1280,20 +1280,20 @@ void SetupChatListSwipe( Ui::AddSkip(container); Ui::AddSubsectionTitle(container, tr::lng_settings_swipe_subsection()); - using Type = Dialogs::Ui::SwipeDialogAction; + using Type = Dialogs::Ui::QuickDialogAction; const auto group = std::make_shared>( - Core::App().settings().swipeDialogAction()); + Core::App().settings().quickDialogAction()); container->add( object_ptr( container, group->value() | rpl::map([](Type value) { - return ((value == Dialogs::Ui::SwipeDialogAction::Mute) + return ((value == Dialogs::Ui::QuickDialogAction::Mute) ? tr::lng_settings_swipe_mute - : (value == Dialogs::Ui::SwipeDialogAction::Pin) + : (value == Dialogs::Ui::QuickDialogAction::Pin) ? tr::lng_settings_swipe_pin - : (value == Dialogs::Ui::SwipeDialogAction::Read) + : (value == Dialogs::Ui::QuickDialogAction::Read) ? tr::lng_settings_swipe_read - : (value == Dialogs::Ui::SwipeDialogAction::Archive) + : (value == Dialogs::Ui::QuickDialogAction::Archive) ? tr::lng_settings_swipe_archive : tr::lng_settings_swipe_disabled)(); }) | rpl::flatten_latest(), @@ -1318,7 +1318,7 @@ void SetupChatListSwipe( addRadio(Type::Delete, tr::lng_settings_swipe_delete); addRadio(Type::Disabled, tr::lng_settings_swipe_disabled); box->addButton(tr::lng_settings_save(), [=] { - Core::App().settings().setSwipeDialogAction( + Core::App().settings().setQuickDialogAction( group->current()); Core::App().saveSettingsDelayed(); box->closeBox(); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 1e66b314a3..bf9053fed2 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -101,9 +101,9 @@ PRIVATE dialogs/ui/chat_search_empty.h dialogs/ui/chat_search_in.cpp dialogs/ui/chat_search_in.h + dialogs/ui/dialogs_quick_action_context.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h - dialogs/ui/dialogs_swipe_context.h dialogs/ui/top_peers_strip.cpp dialogs/ui/top_peers_strip.h From a21eb9d59e7a0d54839771ba1b05bd203eaa4a4e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Mar 2025 10:00:34 +0300 Subject: [PATCH 065/122] Moved out quick actions enum for dialog to separated module. --- Telegram/SourceFiles/core/core_settings.h | 2 +- .../dialogs/ui/dialogs_quick_action.h | 23 +++++++++++++++++++ .../dialogs/ui/dialogs_quick_action_context.h | 10 +------- Telegram/cmake/td_ui.cmake | 1 + 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_quick_action.h diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index bcfa4d05b9..8ce8d72818 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings_proxy.h" #include "media/media_common.h" -#include "dialogs/ui/dialogs_quick_action_context.h" +#include "dialogs/ui/dialogs_quick_action.h" #include "window/themes/window_themes_embedded.h" #include "ui/chat/attach/attach_send_files_way.h" #include "base/flags.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action.h new file mode 100644 index 0000000000..7c924c06ee --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action.h @@ -0,0 +1,23 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Dialogs::Ui { + +using namespace ::Ui; + +enum class QuickDialogAction { + Mute, + Pin, + Read, + Archive, + Delete, + Disabled, +}; + +} // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h index a8f5a5e9ff..945300a8da 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "dialogs/ui/dialogs_quick_action.h" #include "ui/controls/swipe_handler_data.h" namespace Lottie { @@ -17,15 +18,6 @@ namespace Dialogs::Ui { using namespace ::Ui; -enum class QuickDialogAction { - Mute, - Pin, - Read, - Archive, - Delete, - Disabled, -}; - enum class QuickDialogActionLabel { Mute, Unmute, diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index bf9053fed2..82ac02aac5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -102,6 +102,7 @@ PRIVATE dialogs/ui/chat_search_in.cpp dialogs/ui/chat_search_in.h dialogs/ui/dialogs_quick_action_context.h + dialogs/ui/dialogs_quick_action.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h dialogs/ui/top_peers_strip.cpp From 418dcedc4eb37c80878a77886c8edf95f7dfe4b9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Mar 2025 11:33:56 +0300 Subject: [PATCH 066/122] Replaced single QuickActionContext in dialogs widget with flat map. --- .../dialogs/dialogs_inner_widget.cpp | 84 ++++++++++++------- .../dialogs/dialogs_inner_widget.h | 10 ++- .../SourceFiles/dialogs/dialogs_widget.cpp | 4 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 17 ++-- .../SourceFiles/dialogs/ui/dialogs_layout.h | 2 +- .../dialogs/ui/dialogs_quick_action_context.h | 2 +- 6 files changed, 75 insertions(+), 44 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 47791b964b..f8f477143c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -810,7 +810,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto ms = crl::now(); const auto childListShown = _childListShown.current(); auto context = Ui::PaintContext{ - .quickActionContext = _quickActionContext, .st = _st, .topicJumpCache = _topicJumpCache.get(), .folder = _openedFolder, @@ -839,6 +838,12 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto expanding = forum && (key.history()->peer->id == childListShown.peerId); context.rightButton = maybeCacheRightButton(row); + if (key.history()) { + const auto it = _quickActions.find(key.history()->peer->id.value); + if (it != _quickActions.end()) { + context.quickActionContext = it->second.get(); + } + } context.st = (forum ? &st::forumDialogRow : _st.get()); @@ -4995,24 +5000,43 @@ rpl::producer InnerWidget::openBotMainAppRequests() const { return _openBotMainAppRequests.events(); } -void InnerWidget::setSwipeContextData(Ui::Controls::SwipeContextData data) { - _quickActionContext.data = std::move(data); - if (_quickActionContext.data.msgBareId) { +void InnerWidget::setSwipeContextData( + int64 key, + std::optional data) { + if (!key) { + return; + } + if (!data) { + _quickActions.remove(key); + return; + } + const auto it = _quickActions.find(key); + auto context = (Ui::QuickActionContext*)(nullptr); + if (it == _quickActions.end()) { + context = _quickActions.emplace( + key, + std::make_unique()).first->second.get(); + } else { + context = it->second.get(); + } + + context->data = base::take(*data); + if (context->data.msgBareId) { constexpr auto kStartAnimateThreshold = 0.32; constexpr auto kResetAnimateThreshold = 0.24; - if (_quickActionContext.data.ratio > kStartAnimateThreshold) { - if (_quickActionContext.icon - && !_quickActionContext.icon->frameIndex() - && !_quickActionContext.icon->animating()) { - _quickActionContext.icon->animate( + if (context->data.ratio > kStartAnimateThreshold) { + if (context->icon + && !context->icon->frameIndex() + && !context->icon->animating()) { + context->icon->animate( [=] { update(); }, 0, - _quickActionContext.icon->framesCount()); + context->icon->framesCount()); } - } else if (_quickActionContext.data.ratio < kResetAnimateThreshold) { - if (_quickActionContext.icon - && _quickActionContext.icon->frameIndex()) { - _quickActionContext.icon->jumpTo(0, [=] { update(); }); + } else if (context->data.ratio < kResetAnimateThreshold) { + if (context->icon + && context->icon->frameIndex()) { + context->icon->jumpTo(0, [=] { update(); }); } } update(); @@ -5038,23 +5062,25 @@ int64 InnerWidget::calcSwipeKey(int top) { void InnerWidget::prepareQuickAction( int64 key, Dialogs::Ui::QuickDialogAction action) { - if (key) { - const auto peer = session().data().peer(PeerId(key)); - auto name = ResolveQuickDialogLottieIconName(peer, action, _filterId); - _quickActionLottieIcon = Lottie::MakeIcon({ - .name = std::move(name), - .sizeOverride = Size(st::dialogsQuickActionSize), - }); - _quickActionContext.icon = _quickActionLottieIcon.get(); - _quickActionContext.action = action; + Expects(key != 0); + + const auto it = _quickActions.find(key); + auto context = (Ui::QuickActionContext*)(nullptr); + if (it == _quickActions.end()) { + context = _quickActions.emplace( + key, + std::make_unique()).first->second.get(); } else { - if (_quickActionContext.icon) { - _quickActionContext = {}; - } - if (_quickActionLottieIcon) { - _quickActionLottieIcon.reset(); - } + context = it->second.get(); } + + const auto peer = session().data().peer(PeerId(key)); + auto name = ResolveQuickDialogLottieIconName(peer, action, _filterId); + context->icon = Lottie::MakeIcon({ + .name = std::move(name), + .sizeOverride = Size(st::dialogsSwipeActionSize), + }); + context->action = action; } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 8b239d4440..e29f1927ef 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -216,7 +216,9 @@ public: [[nodiscard]] rpl::producer openBotMainAppRequests() const; - void setSwipeContextData(Ui::Controls::SwipeContextData data); + void setSwipeContextData( + int64 key, + std::optional data); [[nodiscard]] int64 calcSwipeKey(int top); void prepareQuickAction(int64 key, Dialogs::Ui::QuickDialogAction); @@ -480,6 +482,8 @@ private: void saveChatsFilterScrollState(FilterId filterId); void restoreChatsFilterScrollState(FilterId filterId); + [[nodiscard]] Ui::QuickActionContext *ensureQuickAction(int64 key); + [[nodiscard]] bool lookupIsInBotAppButton( Row *row, QPoint localPosition); @@ -623,8 +627,8 @@ private: rpl::event_stream<> _refreshHashtagsRequests; rpl::event_stream _openBotMainAppRequests; - Dialogs::Ui::QuickActionContext _quickActionContext; - std::unique_ptr _quickActionLottieIcon = nullptr; + using QuickActionPtr = std::unique_ptr; + base::flat_map _quickActions; RowDescriptor _chatPreviewRow; bool _chatPreviewScheduled = false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index ab58ceed76..a2b9413e7c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -694,7 +694,7 @@ void Widget::setupSwipeBack() { && _inner && (Core::App().settings().quickDialogAction() != Ui::QuickDialogAction::Disabled)) { - _inner->setSwipeContextData(std::move(data)); + _inner->setSwipeContextData(data.msgBareId, std::move(data)); } else { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -716,7 +716,7 @@ void Widget::setupSwipeBack() { _swipeBackData = {}; } if (_inner) { - _inner->setSwipeContextData({}); + _inner->setSwipeContextData(data.msgBareId, std::nullopt); _inner->update(); } } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 8ab33852ef..12ce253ed4 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -395,10 +395,11 @@ void PaintRow( : context.currentBg; auto swipeTranslation = 0; if (history + && context.quickActionContext && (history->peer->id.value - == context.quickActionContext.data.msgBareId)) { - if (context.quickActionContext.data.translation != 0) { - swipeTranslation = context.quickActionContext.data.translation + == context.quickActionContext->data.msgBareId)) { + if (context.quickActionContext->data.translation != 0) { + swipeTranslation = context.quickActionContext->data.translation * -2; } } @@ -883,14 +884,14 @@ void PaintRow( p.setClipRegion(swipeActionRect); const auto labelType = ResolveQuickDialogLabel( history, - context.quickActionContext.action, + context.quickActionContext->action, context.filter); p.fillRect(swipeActionRect, ResolveQuickActionBg(labelType)); - if (context.quickActionContext.data.reachRatio) { + if (context.quickActionContext->data.reachRatio) { p.setPen(Qt::NoPen); p.setBrush(ResolveQuickActionBgActive(labelType)); const auto r = swipeTranslation - * context.quickActionContext.data.reachRatio; + * context.quickActionContext->data.reachRatio; const auto offset = st::dialogsQuickActionSize + st::dialogsQuickActionSize / 2.; p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r); @@ -899,8 +900,8 @@ void PaintRow( - st::dialogsQuickActionSize) / 2; const auto topTranslation = iconOffset / 2.; p.translate(0, -topTranslation); - if (context.quickActionContext.icon) { - context.quickActionContext.icon->paint( + if (context.quickActionContext->icon) { + context.quickActionContext->icon->paint( p, rect::right(geometry) - iconOffset diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index 848ac257f1..43122350ef 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -55,7 +55,7 @@ struct TopicJumpCache { struct PaintContext { RightButton *rightButton = nullptr; std::vector *chatsFilterTags = nullptr; - QuickActionContext quickActionContext; + QuickActionContext *quickActionContext = nullptr; not_null st; TopicJumpCache *topicJumpCache = nullptr; Data::Folder *folder = nullptr; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h index 945300a8da..fec3a1f622 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h @@ -33,7 +33,7 @@ enum class QuickDialogActionLabel { struct QuickActionContext { ::Ui::Controls::SwipeContextData data; - Lottie::Icon *icon = nullptr; + std::unique_ptr icon; QuickDialogAction action; }; From c332b7cb40b5353a2e649f0f7d38064025fc1fd2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Mar 2025 12:57:02 +0300 Subject: [PATCH 067/122] Added initial handler for middle mouse click on dialogs. --- Telegram/SourceFiles/dialogs/dialogs.style | 1 + .../dialogs/dialogs_inner_widget.cpp | 95 ++++++++++++++----- .../dialogs/dialogs_inner_widget.h | 4 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 1 + .../dialogs/ui/dialogs_quick_action_context.h | 5 + 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index d537bfeada..7426ca529b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -791,3 +791,4 @@ dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) { } dialogsQuickActionSize: 20px; +dialogsQuickActionRippleSize: 80px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f8f477143c..28ac0119fa 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -840,9 +840,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) { context.rightButton = maybeCacheRightButton(row); if (key.history()) { const auto it = _quickActions.find(key.history()->peer->id.value); - if (it != _quickActions.end()) { - context.quickActionContext = it->second.get(); - } + context.quickActionContext = (it != _quickActions.end()) + ? it->second.get() + : nullptr; } context.st = (forum ? &st::forumDialogRow : _st.get()); @@ -1850,7 +1850,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { }; const auto origin = e->pos() - QPoint(0, dialogsOffset() + _pressed->top()); - if (addBotAppRipple(origin, updateCallback)) { + if ((_pressButton == Qt::MiddleButton) + && addQuickActionRipple(row, updateCallback)) { + } else if (addBotAppRipple(origin, updateCallback)) { } else if (_pressedTopicJump) { row->addTopicJumpRipple( origin, @@ -1922,7 +1924,7 @@ bool InnerWidget::addBotAppRipple(QPoint origin, Fn updateCallback) { _pressedBotAppData->ripple = std::make_unique( st::defaultRippleAnimation, Ui::RippleAnimation::RoundRectMask(size, size.height() / 2), - updateCallback); + std::move(updateCallback)); } const auto shift = QPoint( width() - size.width() - st::dialogRowOpenBotRight, @@ -1931,6 +1933,43 @@ bool InnerWidget::addBotAppRipple(QPoint origin, Fn updateCallback) { return true; } +bool InnerWidget::addQuickActionRipple( + not_null row, + Fn updateCallback) { + const auto action = Core::App().settings().quickDialogAction(); + if (action == Dialogs::Ui::QuickDialogAction::Disabled) { + return false; + } + const auto history = row->history(); + if (!history) { + return false; + } + const auto key = history->peer->id.value; + const auto context = ensureQuickAction(key); + + auto name = ResolveQuickDialogLottieIconName( + history->peer, + action, + _filterId); + context->icon = Lottie::MakeIcon({ + .name = std::move(name), + .sizeOverride = Size(st::dialogsQuickActionSize), + }); + context->action = action; + const auto size = QSize( + st::dialogsQuickActionRippleSize, + row->height()); + if (!context->ripple) { + context->ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RectMask(size), + std::move(updateCallback)); + } + context->ripple->add(QPoint(size.width() / 2, size.height() / 2)); + + return true; +} + const std::vector &InnerWidget::pinnedChatsOrder() const { const auto owner = &session().data(); return _savedSublists @@ -2236,6 +2275,16 @@ void InnerWidget::mousePressReleased( if (_pressedBotAppData && _pressedBotAppData->ripple) { _pressedBotAppData->ripple->lastStop(); } + if (!_quickActions.empty() && pressed) { + if (const auto history = pressed->history()) { + const auto it = _quickActions.find(history->peer->id.value); + if (it != _quickActions.end()) { + if (it->second->ripple) { + it->second->ripple->lastStop(); + } + } + } + } updateSelectedRow(); if (!wasDragging && button == Qt::LeftButton) { if ((collapsedPressed >= 0 && collapsedPressed == _collapsedSelected) @@ -5010,15 +5059,7 @@ void InnerWidget::setSwipeContextData( _quickActions.remove(key); return; } - const auto it = _quickActions.find(key); - auto context = (Ui::QuickActionContext*)(nullptr); - if (it == _quickActions.end()) { - context = _quickActions.emplace( - key, - std::make_unique()).first->second.get(); - } else { - context = it->second.get(); - } + const auto context = ensureQuickAction(key); context->data = base::take(*data); if (context->data.msgBareId) { @@ -5043,6 +5084,19 @@ void InnerWidget::setSwipeContextData( } } +not_null InnerWidget::ensureQuickAction(int64 key) { + Expects(key != 0); + + const auto it = _quickActions.find(key); + if (it == _quickActions.end()) { + return _quickActions.emplace( + key, + std::make_unique()).first->second.get(); + } else { + return it->second.get(); + } +} + int64 InnerWidget::calcSwipeKey(int top) { top -= dialogsOffset(); for (auto it = _shownList->begin(); it != _shownList->end(); ++it) { @@ -5064,21 +5118,12 @@ void InnerWidget::prepareQuickAction( Dialogs::Ui::QuickDialogAction action) { Expects(key != 0); - const auto it = _quickActions.find(key); - auto context = (Ui::QuickActionContext*)(nullptr); - if (it == _quickActions.end()) { - context = _quickActions.emplace( - key, - std::make_unique()).first->second.get(); - } else { - context = it->second.get(); - } - + const auto context = ensureQuickAction(key); const auto peer = session().data().peer(PeerId(key)); auto name = ResolveQuickDialogLottieIconName(peer, action, _filterId); context->icon = Lottie::MakeIcon({ .name = std::move(name), - .sizeOverride = Size(st::dialogsSwipeActionSize), + .sizeOverride = Size(st::dialogsQuickActionSize), }); context->action = action; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index e29f1927ef..c3bd31867a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -352,6 +352,7 @@ private: void repaintDialogRowCornerStatus(not_null history); bool addBotAppRipple(QPoint origin, Fn updateCallback); + bool addQuickActionRipple(not_null row, Fn updateCallback); void setupShortcuts(); RowDescriptor computeJump( @@ -482,7 +483,8 @@ private: void saveChatsFilterScrollState(FilterId filterId); void restoreChatsFilterScrollState(FilterId filterId); - [[nodiscard]] Ui::QuickActionContext *ensureQuickAction(int64 key); + [[nodiscard]] not_null ensureQuickAction( + int64 key); [[nodiscard]] bool lookupIsInBotAppButton( Row *row, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 12ce253ed4..793de8b605 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -396,6 +396,7 @@ void PaintRow( auto swipeTranslation = 0; if (history && context.quickActionContext + && !context.quickActionContext->ripple && (history->peer->id.value == context.quickActionContext->data.msgBareId)) { if (context.quickActionContext->data.translation != 0) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h index fec3a1f622..fbd0c363cf 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h @@ -14,6 +14,10 @@ namespace Lottie { class Icon; } // namespace Lottie +namespace Ui { +class RippleAnimation; +} // namespace Ui + namespace Dialogs::Ui { using namespace ::Ui; @@ -34,6 +38,7 @@ enum class QuickDialogActionLabel { struct QuickActionContext { ::Ui::Controls::SwipeContextData data; std::unique_ptr icon; + std::unique_ptr ripple; QuickDialogAction action; }; From c11a7589e2e76614f35b0b80ab4eafd7418282f5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Mar 2025 12:57:05 +0300 Subject: [PATCH 068/122] Added initial ability to trigger quick dialog action with middle click. --- .../dialogs/dialogs_inner_widget.cpp | 50 ++++++++++++--- .../dialogs/dialogs_quick_action.cpp | 61 +++++++++++++++++++ .../dialogs/dialogs_quick_action.h | 10 +++ .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 57 ++++++++--------- .../dialogs/ui/dialogs_quick_action_context.h | 1 + 5 files changed, 141 insertions(+), 38 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 28ac0119fa..fbb41c763c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1956,16 +1956,40 @@ bool InnerWidget::addQuickActionRipple( .sizeOverride = Size(st::dialogsQuickActionSize), }); context->action = action; - const auto size = QSize( - st::dialogsQuickActionRippleSize, - row->height()); - if (!context->ripple) { - context->ripple = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RectMask(size), - std::move(updateCallback)); - } - context->ripple->add(QPoint(size.width() / 2, size.height() / 2)); + context->icon->jumpTo(context->icon->framesCount() - 1, [=] { + const auto size = QSize( + st::dialogsQuickActionRippleSize, + row->height()); + if (!context->ripple) { + context->ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RectMask(size), + updateCallback); + } + if (!context->rippleFg) { + context->rippleFg = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::MaskByDrawer( + size, + true, + [&](QPainter &p) { + p.setCompositionMode( + QPainter::CompositionMode_Source); + p.fillRect(Rect(size), Qt::transparent); + DrawQuickAction( + p, + Rect(size), + context->icon.get(), + ResolveQuickDialogLabel( + row->history(), + action, + _filterId)); + }), + std::move(updateCallback)); + } + context->ripple->add(QPoint(size.width() / 2, size.height() / 2)); + context->rippleFg->add(QPoint(size.width() / 2, size.height() / 2)); + }); return true; } @@ -2281,7 +2305,13 @@ void InnerWidget::mousePressReleased( if (it != _quickActions.end()) { if (it->second->ripple) { it->second->ripple->lastStop(); + it->second->rippleFg->lastStop(); } + PerformQuickDialogAction( + _controller, + history->peer, + it->second->action, + _filterId); } } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index 8bafb68690..1d5f16e107 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -14,13 +14,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "dialogs/dialogs_entry.h" #include "history/history.h" +#include "lang/lang_instance.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "main/main_session.h" #include "menu/menu_mute.h" #include "window/window_peer_menu.h" #include "window/window_session_controller.h" +#include "styles/style_dialogs.h" namespace Dialogs { +namespace { + +const style::font &SwipeActionFont( + Dialogs::Ui::QuickDialogActionLabel action, + int availableWidth) { + struct Entry final { + Dialogs::Ui::QuickDialogActionLabel action; + QString langId; + style::font font; + }; + static auto Fonts = std::vector(); + for (auto &entry : Fonts) { + if (entry.action == action) { + if (entry.langId == Lang::GetInstance().id()) { + return entry.font; + } + } + } + constexpr auto kNormalFontSize = 13; + constexpr auto kMinFontSize = 5; + for (auto i = kNormalFontSize; i >= kMinFontSize; --i) { + auto font = style::font( + style::ConvertScale(i, style::Scale()), + st::semiboldFont->flags(), + st::semiboldFont->family()); + if (font->width(ResolveQuickDialogLabel(action)) <= availableWidth + || i == kMinFontSize) { + Fonts.emplace_back(Entry{ + .action = action, + .langId = Lang::GetInstance().id(), + .font = std::move(font), + }); + return Fonts.back().font; + } + } + Unexpected("SwipeActionFont: can't find font."); +} + +} // namespace void PerformQuickDialogAction( not_null controller, @@ -163,4 +205,23 @@ const style::color &ResolveQuickActionBgActive( return st::windowSubTextFgOver; } +void DrawQuickAction( + QPainter &p, + const QRect &rect, + not_null icon, + Ui::QuickDialogActionLabel label) { + const auto iconSize = st::dialogsQuickActionSize; + const auto innerHeight = iconSize * 2; + const auto top = (rect.height() - innerHeight) / 2; + icon->paint(p, rect.x() + (rect.width() - iconSize) / 2, top); + p.setPen(st::premiumButtonFg); + p.setBrush(Qt::NoBrush); + const auto availableWidth = rect.width(); + p.setFont(SwipeActionFont(label, availableWidth)); + p.drawText( + QRect(rect.x(), top, availableWidth, innerHeight), + ResolveQuickDialogLabel(label), + style::al_bottom); +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.h b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h index e425cd08dc..3d5cb80ddf 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.h +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h @@ -15,6 +15,10 @@ enum class QuickDialogAction; enum class QuickDialogActionLabel; } // namespace Dialogs::Ui +namespace Lottie { +class Icon; +} // namespace Lottie + namespace Window { class SessionController; } // namespace Window @@ -44,4 +48,10 @@ void PerformQuickDialogAction( [[nodiscard]] const style::color &ResolveQuickActionBgActive( Ui::QuickDialogActionLabel); +void DrawQuickAction( + QPainter &p, + const QRect &rect, + not_null icon, + Ui::QuickDialogActionLabel label); + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 793de8b605..c559693051 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -878,7 +878,7 @@ void PaintRow( if (swipeTranslation) { p.translate(swipeTranslation, 0); const auto swipeActionRect = QRect( - geometry.x() + geometry.width() - swipeTranslation, + rect::right(geometry) - swipeTranslation, geometry.y(), swipeTranslation, geometry.height()); @@ -897,35 +897,36 @@ void PaintRow( + st::dialogsQuickActionSize / 2.; p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r); } - const auto iconOffset = (geometry.height() - - st::dialogsQuickActionSize) / 2; - const auto topTranslation = iconOffset / 2.; - p.translate(0, -topTranslation); - if (context.quickActionContext->icon) { - context.quickActionContext->icon->paint( - p, - rect::right(geometry) - - iconOffset - - st::dialogsQuickActionSize, - iconOffset, - st::premiumButtonFg->c); - } - { - p.setPen(st::premiumButtonFg); - p.setBrush(Qt::NoBrush); - const auto left = rect::right(geometry) - - iconOffset * 2 - - st::dialogsQuickActionSize; - const auto availableWidth = geometry.width() - left; - p.setFont(SwipeActionFont(labelType, availableWidth)); - p.drawText( - QRect(left, 0, availableWidth, geometry.height()), - ResolveQuickDialogLabel(labelType), - style::al_bottom); - } - p.translate(0, topTranslation); + const auto quickWidth = st::dialogsQuickActionSize * 3; + DrawQuickAction( + p, + QRect( + rect::right(geometry) - quickWidth, + geometry.y(), + quickWidth, + geometry.height()), + context.quickActionContext->icon.get(), + labelType); p.setClipping(false); } + if (const auto quick = context.quickActionContext; + quick && quick->ripple && quick->rippleFg) { + const auto labelType = ResolveQuickDialogLabel( + history, + context.quickActionContext->action, + context.filter); + const auto ripple = ResolveQuickActionBg(labelType); + const auto size = st::dialogsQuickActionRippleSize; + const auto x = geometry.width() - size; + quick->ripple->paint(p, x, 0, size, &ripple->c); + quick->rippleFg->paint(p, x, 0, size, &st::premiumButtonFg->c); + if (quick->ripple->empty()) { + quick->ripple.reset(); + } + if (quick->rippleFg->empty()) { + quick->rippleFg.reset(); + } + } } } // namespace diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h index fbd0c363cf..d60b6974f5 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h @@ -39,6 +39,7 @@ struct QuickActionContext { ::Ui::Controls::SwipeContextData data; std::unique_ptr icon; std::unique_ptr ripple; + std::unique_ptr rippleFg; QuickDialogAction action; }; From feb1d4ebccfb75e872178fa04fff6829b84bec8b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 Mar 2025 17:35:25 +0300 Subject: [PATCH 069/122] Fixed dialog dragging with pressed quick dialog action. --- .../dialogs/dialogs_inner_widget.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index fbb41c763c..783d3d3272 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2015,6 +2015,11 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { < style::ConvertScale(kStartReorderThreshold)) { return; } + if ((_pressButton == Qt::MiddleButton) + && (Core::App().settings().quickDialogAction() + != Dialogs::Ui::QuickDialogAction::Disabled)) { + return; + } _dragging = _pressed; startReorderPinned(localPosition); } @@ -2307,11 +2312,13 @@ void InnerWidget::mousePressReleased( it->second->ripple->lastStop(); it->second->rippleFg->lastStop(); } - PerformQuickDialogAction( - _controller, - history->peer, - it->second->action, - _filterId); + if (pressed == _selected) { + PerformQuickDialogAction( + _controller, + history->peer, + it->second->action, + _filterId); + } } } } From 520b4f92ab9f4d19c5dd3cabae38f7eedbb508c4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Mar 2025 16:38:24 +0300 Subject: [PATCH 070/122] Improved phrases for quick dialog actions in settings. --- Telegram/Resources/langs/lang.strings | 23 +++++------ .../dialogs/dialogs_quick_action.cpp | 20 +++++----- .../SourceFiles/settings/settings_chat.cpp | 38 ++++++++++++------- 3 files changed, 47 insertions(+), 34 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0763000c0b..f21eeed320 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1180,17 +1180,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_restart_now" = "Restart"; "lng_settings_restart_later" = "Later"; -"lng_settings_swipe_subsection" = "Chat list swipe gesture"; -"lng_settings_swipe_mute" = "Mute"; -"lng_settings_swipe_unmute" = "Unmute"; -"lng_settings_swipe_pin" = "Pin"; -"lng_settings_swipe_unpin" = "Unpin"; -"lng_settings_swipe_read" = "Read"; -"lng_settings_swipe_unread" = "Unread"; -"lng_settings_swipe_archive" = "Archive"; -"lng_settings_swipe_unarchive" = "Unarchive"; -"lng_settings_swipe_delete" = "Delete"; -"lng_settings_swipe_disabled" = "Change folder"; +"lng_settings_quick_dialog_action_title" = "Chat list quick action"; +"lng_settings_quick_dialog_action_about" = "Choose the action you want to perform when you middle-click or swipe left in the chat list."; +"lng_settings_quick_dialog_action_mute" = "Mute"; +"lng_settings_quick_dialog_action_unmute" = "Unmute"; +"lng_settings_quick_dialog_action_pin" = "Pin"; +"lng_settings_quick_dialog_action_unpin" = "Unpin"; +"lng_settings_quick_dialog_action_read" = "Read"; +"lng_settings_quick_dialog_action_unread" = "Unread"; +"lng_settings_quick_dialog_action_archive" = "Archive"; +"lng_settings_quick_dialog_action_unarchive" = "Unarchive"; +"lng_settings_quick_dialog_action_delete" = "Delete"; +"lng_settings_quick_dialog_action_disabled" = "Change folder"; "lng_sessions_header" = "This device"; "lng_sessions_other_header" = "Active Devices"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index 1d5f16e107..4c7a6d8e6c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -158,25 +158,25 @@ Ui::QuickDialogActionLabel ResolveQuickDialogLabel( QString ResolveQuickDialogLabel(Ui::QuickDialogActionLabel action) { switch (action) { case Ui::QuickDialogActionLabel::Mute: - return tr::lng_settings_swipe_mute(tr::now); + return tr::lng_settings_quick_dialog_action_mute(tr::now); case Ui::QuickDialogActionLabel::Unmute: - return tr::lng_settings_swipe_unmute(tr::now); + return tr::lng_settings_quick_dialog_action_unmute(tr::now); case Ui::QuickDialogActionLabel::Pin: - return tr::lng_settings_swipe_pin(tr::now); + return tr::lng_settings_quick_dialog_action_pin(tr::now); case Ui::QuickDialogActionLabel::Unpin: - return tr::lng_settings_swipe_unpin(tr::now); + return tr::lng_settings_quick_dialog_action_unpin(tr::now); case Ui::QuickDialogActionLabel::Read: - return tr::lng_settings_swipe_read(tr::now); + return tr::lng_settings_quick_dialog_action_read(tr::now); case Ui::QuickDialogActionLabel::Unread: - return tr::lng_settings_swipe_unread(tr::now); + return tr::lng_settings_quick_dialog_action_unread(tr::now); case Ui::QuickDialogActionLabel::Archive: - return tr::lng_settings_swipe_archive(tr::now); + return tr::lng_settings_quick_dialog_action_archive(tr::now); case Ui::QuickDialogActionLabel::Unarchive: - return tr::lng_settings_swipe_unarchive(tr::now); + return tr::lng_settings_quick_dialog_action_unarchive(tr::now); case Ui::QuickDialogActionLabel::Delete: - return tr::lng_settings_swipe_delete(tr::now); + return tr::lng_settings_quick_dialog_action_delete(tr::now); default: - return tr::lng_settings_swipe_disabled(tr::now); + return tr::lng_settings_quick_dialog_action_disabled(tr::now); }; } diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index bf803cc512..33e4f9c2c7 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -1278,7 +1278,9 @@ void SetupChatListSwipe( not_null container) { Ui::AddDivider(container); Ui::AddSkip(container); - Ui::AddSubsectionTitle(container, tr::lng_settings_swipe_subsection()); + Ui::AddSubsectionTitle( + container, + tr::lng_settings_quick_dialog_action_title()); using Type = Dialogs::Ui::QuickDialogAction; const auto group = std::make_shared>( @@ -1288,19 +1290,19 @@ void SetupChatListSwipe( container, group->value() | rpl::map([](Type value) { return ((value == Dialogs::Ui::QuickDialogAction::Mute) - ? tr::lng_settings_swipe_mute + ? tr::lng_settings_quick_dialog_action_mute : (value == Dialogs::Ui::QuickDialogAction::Pin) - ? tr::lng_settings_swipe_pin + ? tr::lng_settings_quick_dialog_action_pin : (value == Dialogs::Ui::QuickDialogAction::Read) - ? tr::lng_settings_swipe_read + ? tr::lng_settings_quick_dialog_action_read : (value == Dialogs::Ui::QuickDialogAction::Archive) - ? tr::lng_settings_swipe_archive - : tr::lng_settings_swipe_disabled)(); + ? tr::lng_settings_quick_dialog_action_archive + : tr::lng_settings_quick_dialog_action_disabled)(); }) | rpl::flatten_latest(), st::settingsButtonNoIcon) )->setClickedCallback([=] { controller->uiShow()->showBox(Box([=](not_null box) { - box->setTitle(tr::lng_settings_swipe_subsection()); + box->setTitle(tr::lng_settings_quick_dialog_action_title()); const auto addRadio = [&](Type value, tr::phrase<> phrase) { box->verticalLayout()->add( object_ptr>( @@ -1311,12 +1313,18 @@ void SetupChatListSwipe( st::settingsSendType), st::settingsSendTypePadding); }; - addRadio(Type::Mute, tr::lng_settings_swipe_mute); - addRadio(Type::Pin, tr::lng_settings_swipe_pin); - addRadio(Type::Read, tr::lng_settings_swipe_read); - addRadio(Type::Archive, tr::lng_settings_swipe_archive); - addRadio(Type::Delete, tr::lng_settings_swipe_delete); - addRadio(Type::Disabled, tr::lng_settings_swipe_disabled); + addRadio(Type::Mute, tr::lng_settings_quick_dialog_action_mute); + addRadio(Type::Pin, tr::lng_settings_quick_dialog_action_pin); + addRadio(Type::Read, tr::lng_settings_quick_dialog_action_read); + addRadio( + Type::Archive, + tr::lng_settings_quick_dialog_action_archive); + addRadio( + Type::Delete, + tr::lng_settings_quick_dialog_action_delete); + addRadio( + Type::Disabled, + tr::lng_settings_quick_dialog_action_disabled); box->addButton(tr::lng_settings_save(), [=] { Core::App().settings().setQuickDialogAction( group->current()); @@ -1327,6 +1335,10 @@ void SetupChatListSwipe( })); }); Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_settings_quick_dialog_action_about()); + Ui::AddSkip(container); } void SetupDefaultThemes( From 889f11130043654b3ce595b18b7a88abb7f5727b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Mar 2025 16:59:09 +0300 Subject: [PATCH 071/122] Slightly improved saving of quick dialog action in settings. --- Telegram/SourceFiles/settings/settings_chat.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 33e4f9c2c7..89399c0370 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -706,7 +706,6 @@ void ChooseFromFile( void SetupStickersEmoji( not_null controller, not_null container) { - Ui::AddDivider(container); Ui::AddSkip(container); Ui::AddSubsectionTitle(container, tr::lng_settings_stickers_emoji()); @@ -1285,6 +1284,10 @@ void SetupChatListSwipe( using Type = Dialogs::Ui::QuickDialogAction; const auto group = std::make_shared>( Core::App().settings().quickDialogAction()); + group->setChangedCallback([=](Type value) { + Core::App().settings().setQuickDialogAction(value); + Core::App().saveSettings(); + }); container->add( object_ptr( container, @@ -1325,13 +1328,7 @@ void SetupChatListSwipe( addRadio( Type::Disabled, tr::lng_settings_quick_dialog_action_disabled); - box->addButton(tr::lng_settings_save(), [=] { - Core::App().settings().setQuickDialogAction( - group->current()); - Core::App().saveSettingsDelayed(); - box->closeBox(); - }); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); })); }); Ui::AddSkip(container); From cdf27296e40c9c716b1433aae1e4fa5a1dc2234d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 18:04:14 +0400 Subject: [PATCH 072/122] Fix deselecting dialog rows between them. --- Telegram/SourceFiles/dialogs/dialogs_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index ce6a724625..e3c8044413 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -198,7 +198,7 @@ Row *List::rowAtY(int y) const { List::iterator List::findByY(int y) const { return ranges::lower_bound(_rows, y, ranges::less(), [](const Row *row) { - return row->top() + row->height(); + return row->top() + row->height() - 1; }); } From b057d4fcb7eb728d69e4b7b8f68ddf34b2fa1780 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 18:23:17 +0400 Subject: [PATCH 073/122] Allow folder switching by swipes in vertical mode. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a2b9413e7c..e68d3df901 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -794,9 +794,7 @@ void Widget::setupSwipeBack() { } }); } - if (_chatFilters - && session().data().chatsFilters().has() - && isDisabled) { + if (session().data().chatsFilters().has() && isDisabled) { _swipeBackMirrored = !isRightToLeft; using namespace Window; const auto next = !isRightToLeft; From 4e4604e00a00b51838c7b73ca4a6395ee6e99974 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Mar 2025 18:37:30 +0400 Subject: [PATCH 074/122] Beta version 5.12.6. - Allow customizing chats list swipe left action. - Fix custom emoji in poll results view. - Fix crash in main menu swipe closing. --- 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 | 6 ++++++ 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 3367d09c3d..aeb613eaa1 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.12.6.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a5ae1ef3cc..49be2360f8 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 5,12,5,0 - PRODUCTVERSION 5,12,5,0 + FILEVERSION 5,12,6,0 + PRODUCTVERSION 5,12,6,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.12.5.0" + VALUE "FileVersion", "5.12.6.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.5.0" + VALUE "ProductVersion", "5.12.6.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 39c1ad93af..092070e203 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 5,12,5,0 - PRODUCTVERSION 5,12,5,0 + FILEVERSION 5,12,6,0 + PRODUCTVERSION 5,12,6,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.12.5.0" + VALUE "FileVersion", "5.12.6.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.5.0" + VALUE "ProductVersion", "5.12.6.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 562c1004a9..df56e7d231 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 = 5012005; -constexpr auto AppVersionStr = "5.12.5"; +constexpr auto AppVersion = 5012006; +constexpr auto AppVersionStr = "5.12.6"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index c614f7e0ae..0ba678f4eb 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5012005 +AppVersion 5012006 AppVersionStrMajor 5.12 -AppVersionStrSmall 5.12.5 -AppVersionStr 5.12.5 +AppVersionStrSmall 5.12.6 +AppVersionStr 5.12.6 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.12.5.beta +AppVersionOriginal 5.12.6.beta diff --git a/changelog.txt b/changelog.txt index 74d420c7eb..17ccc7b0fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.12.6 beta (20.03.25) + +- Allow customizing chats list swipe left action. +- Fix custom emoji in poll results view. +- Fix crash in main menu swipe closing. + 5.12.5 beta (14.03.25) - Support swipe navigation on touchscreens. From 885996c5cfbe46400ec84fb836f9e60c81a8c89e Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 17 Mar 2025 15:39:57 -0400 Subject: [PATCH 075/122] update ada-url to v3.2.1 --- Telegram/build/docker/centos_env/Dockerfile | 3 ++- Telegram/build/prepare/prepare.py | 3 ++- snap/snapcraft.yaml | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 4e2c8f6fbe..b96cda0bb6 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -807,12 +807,13 @@ RUN cmake --build out --config Debug --parallel \ {%- endif %} FROM builder AS ada -RUN git clone -b v2.9.0 --depth=1 {{ GIT }}/ada-url/ada.git \ +RUN git clone -b v3.2.1 --depth=1 {{ GIT }}/ada-url/ada.git \ && cd ada \ && cmake -GNinja -B build . \ -D CMAKE_BUILD_TYPE=None \ -D ADA_TESTING=OFF \ -D ADA_TOOLS=OFF \ + -D ADA_INCLUDE_URL_PATTERN=OFF \ && cmake --build build --parallel \ && DESTDIR="{{ LibrariesPath }}/ada-cache" cmake --install build \ && cd .. \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index e14a2e4b02..3f3a2e48d9 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1864,13 +1864,14 @@ release: """) stage('ada', """ - git clone -b v2.9.0 https://github.com/ada-url/ada.git + git clone -b 3.2.1 https://github.com/ada-url/ada.git cd ada win: cmake -B out . ^ -A %WIN32X64% ^ -D ADA_TESTING=OFF ^ -D ADA_TOOLS=OFF ^ + -D ADA_INCLUDE_URL_PATTERN=OFF ^ -D CMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -D CMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -D CMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index cb8f1ea108..d66b835aff 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -218,7 +218,7 @@ parts: ada: source: https://github.com/ada-url/ada.git source-depth: 1 - source-tag: v2.9.0 + source-tag: v3.2.1 plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s @@ -228,6 +228,7 @@ parts: - -DCMAKE_INSTALL_PREFIX=/usr - -DADA_TESTING=OFF - -DADA_TOOLS=OFF + - -DADA_INCLUDE_URL_PATTERN=OFF prime: - -./usr/include - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/cmake From d6bbc375c4a4e51c636d9cee9d4f73f3b9f39beb Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 17 Mar 2025 20:37:35 -0400 Subject: [PATCH 076/122] Update prepare.py --- Telegram/build/prepare/prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 3f3a2e48d9..af8d0b31ba 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1864,7 +1864,7 @@ release: """) stage('ada', """ - git clone -b 3.2.1 https://github.com/ada-url/ada.git + git clone -b v3.2.1 https://github.com/ada-url/ada.git cd ada win: cmake -B out . ^ From f2016a8aa558ec8effd07c13615ec504c618b88a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Mar 2025 09:34:58 +0400 Subject: [PATCH 077/122] Don't ensure cursor visibility on modifier keys. Fix #29108. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9255267c81..45fa6c356e 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 9255267c813bdc7105e77d0643b5a77e6dc66ad4 +Subproject commit 45fa6c356e25383a28949fe5e275a6ff02b93270 From efc7cc498063516dc1c3cb4a73db2d8a32b48133 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 10:48:25 +0300 Subject: [PATCH 078/122] Added convenient bool operator to SwipeContextData. --- Telegram/SourceFiles/ui/controls/swipe_handler_data.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler_data.h b/Telegram/SourceFiles/ui/controls/swipe_handler_data.h index b90b5351f5..c0f5da0b90 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler_data.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler_data.h @@ -10,6 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui::Controls { struct SwipeContextData final { + [[nodiscard]] bool empty() const { + return !ratio + && !reachRatio + && !translation + && !cursorTop; + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + float64 ratio = 0.; float64 reachRatio = 0.; int64 msgBareId = 0; From 47506d70ed40cd2e69541169356809272b69c536 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 13:43:28 +0300 Subject: [PATCH 079/122] Simplified signature of ResolveQuickDialogLottieIconName. --- .../dialogs/dialogs_inner_widget.cpp | 16 ++++--- .../dialogs/dialogs_quick_action.cpp | 48 ++++++++----------- .../dialogs/dialogs_quick_action.h | 4 +- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 783d3d3272..c18b998478 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1944,13 +1944,14 @@ bool InnerWidget::addQuickActionRipple( if (!history) { return false; } + const auto type = ResolveQuickDialogLabel(history, action, _filterId); + if (type == Dialogs::Ui::QuickDialogActionLabel::Disabled) { + return false; + } const auto key = history->peer->id.value; const auto context = ensureQuickAction(key); - auto name = ResolveQuickDialogLottieIconName( - history->peer, - action, - _filterId); + auto name = ResolveQuickDialogLottieIconName(type); context->icon = Lottie::MakeIcon({ .name = std::move(name), .sizeOverride = Size(st::dialogsQuickActionSize), @@ -5156,8 +5157,11 @@ void InnerWidget::prepareQuickAction( Expects(key != 0); const auto context = ensureQuickAction(key); - const auto peer = session().data().peer(PeerId(key)); - auto name = ResolveQuickDialogLottieIconName(peer, action, _filterId); + auto name = ResolveQuickDialogLottieIconName( + ResolveQuickDialogLabel( + session().data().history(PeerId(key)), + action, + _filterId)); context->icon = Lottie::MakeIcon({ .name = std::move(name), .sizeOverride = Size(st::dialogsQuickActionSize), diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index 4c7a6d8e6c..e1092d5c9d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -95,35 +95,29 @@ void PerformQuickDialogAction( } } -QString ResolveQuickDialogLottieIconName( - not_null peer, - Ui::QuickDialogAction action, - FilterId filterId) { - if (action == Dialogs::Ui::QuickDialogAction::Mute) { - const auto history = peer->owner().history(peer); - const auto isMuted = rpl::variable( - MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); - return isMuted ? u"swipe_unmute"_q : u"swipe_mute"_q; - } else if (action == Dialogs::Ui::QuickDialogAction::Pin) { - const auto history = peer->owner().history(peer); - const auto entry = (Dialogs::Entry*)(history); - return entry->isPinnedDialog(filterId) - ? u"swipe_unpin"_q - : u"swipe_pin"_q; - } else if (action == Dialogs::Ui::QuickDialogAction::Read) { - const auto history = peer->owner().history(peer); - return Window::IsUnreadThread(history) - ? u"swipe_read"_q - : u"swipe_unread"_q; - } else if (action == Dialogs::Ui::QuickDialogAction::Archive) { - const auto history = peer->owner().history(peer); - return Window::IsArchived(history) - ? u"swipe_unarchive"_q - : u"swipe_archive"_q; - } else if (action == Dialogs::Ui::QuickDialogAction::Delete) { +QString ResolveQuickDialogLottieIconName(Ui::QuickDialogActionLabel action) { + switch (action) { + case Ui::QuickDialogActionLabel::Mute: + return u"swipe_mute"_q; + case Ui::QuickDialogActionLabel::Unmute: + return u"swipe_unmute"_q; + case Ui::QuickDialogActionLabel::Pin: + return u"swipe_pin"_q; + case Ui::QuickDialogActionLabel::Unpin: + return u"swipe_unpin"_q; + case Ui::QuickDialogActionLabel::Read: + return u"swipe_read"_q; + case Ui::QuickDialogActionLabel::Unread: + return u"swipe_unread"_q; + case Ui::QuickDialogActionLabel::Archive: + return u"swipe_archive"_q; + case Ui::QuickDialogActionLabel::Unarchive: + return u"swipe_unarchive"_q; + case Ui::QuickDialogActionLabel::Delete: return u"swipe_delete"_q; + default: + return u"swipe_disabled"_q; } - return u"swipe_disabled"_q; } Ui::QuickDialogActionLabel ResolveQuickDialogLabel( diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.h b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h index 3d5cb80ddf..e8d53c06b7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.h +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h @@ -32,9 +32,7 @@ void PerformQuickDialogAction( FilterId filterId); [[nodiscard]] QString ResolveQuickDialogLottieIconName( - not_null peer, - Ui::QuickDialogAction action, - FilterId filterId); + Ui::QuickDialogActionLabel action); [[nodiscard]] Ui::QuickDialogActionLabel ResolveQuickDialogLabel( not_null history, From c085691c54324e996b708c92f2efb601880118d5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 12:26:28 +0300 Subject: [PATCH 080/122] Moved out active quick action from total list of quick dialog actions. --- .../dialogs/dialogs_inner_widget.cpp | 95 +++++++++++++------ .../dialogs/dialogs_inner_widget.h | 4 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 51 +++++----- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 20 ++-- 4 files changed, 106 insertions(+), 64 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index c18b998478..cd8def62ea 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -831,18 +831,34 @@ void InnerWidget::paintEvent(QPaintEvent *e) { bool mayBeActive) { const auto &key = row->key(); const auto active = mayBeActive && isRowActive(row, activeEntry); - const auto forum = key.history() && key.history()->isForum(); + const auto history = key.history(); + const auto forum = history && history->isForum(); if (forum && !_topicJumpCache) { _topicJumpCache = std::make_unique(); } const auto expanding = forum - && (key.history()->peer->id == childListShown.peerId); + && (history->peer->id == childListShown.peerId); context.rightButton = maybeCacheRightButton(row); - if (key.history()) { - const auto it = _quickActions.find(key.history()->peer->id.value); - context.quickActionContext = (it != _quickActions.end()) - ? it->second.get() - : nullptr; + if (history) { + if (_activeQuickAction + && (_activeQuickAction->data.msgBareId + == history->peer->id.value)) { + context.quickActionContext = _activeQuickAction.get(); + } else if (!_inactiveQuickActions.empty()) { + auto it = _inactiveQuickActions.begin(); + while (it != _inactiveQuickActions.end()) { + const auto raw = it->get(); + if ((!raw->ripple || raw->ripple->empty()) + && (!raw->rippleFg || raw->rippleFg->empty())) { + _inactiveQuickActions.erase(it); + } else { + if (raw->data.msgBareId == history->peer->id.value) { + context.quickActionContext = raw; + } + ++it; + } + } + } } context.st = (forum ? &st::forumDialogRow : _st.get()); @@ -868,7 +884,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } if (active && (filter.flags() & Data::ChatFilter::Flag::NoRead) - && !filter.contains(key.history(), true)) { + && !filter.contains(history, true)) { // Hack for History::fakeUnreadWhileOpened(). continue; } @@ -923,6 +939,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) { && _selectedTopicJump && (!_pressed || _pressedTopicJump); Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), context); + if (context.quickActionContext) { + context.quickActionContext = nullptr; + } }; if (_state == WidgetState::Default) { const auto collapsedSkip = collapsedRowsOffset(); @@ -1936,6 +1955,9 @@ bool InnerWidget::addBotAppRipple(QPoint origin, Fn updateCallback) { bool InnerWidget::addQuickActionRipple( not_null row, Fn updateCallback) { + if (_activeQuickAction) { + return false; + } const auto action = Core::App().settings().quickDialogAction(); if (action == Dialogs::Ui::QuickDialogAction::Disabled) { return false; @@ -1950,6 +1972,9 @@ bool InnerWidget::addQuickActionRipple( } const auto key = history->peer->id.value; const auto context = ensureQuickAction(key); + if (context->data) { + return false; + } auto name = ResolveQuickDialogLottieIconName(type); context->icon = Lottie::MakeIcon({ @@ -2305,22 +2330,25 @@ void InnerWidget::mousePressReleased( if (_pressedBotAppData && _pressedBotAppData->ripple) { _pressedBotAppData->ripple->lastStop(); } - if (!_quickActions.empty() && pressed) { + if (_activeQuickAction && pressed && !_activeQuickAction->data) { if (const auto history = pressed->history()) { - const auto it = _quickActions.find(history->peer->id.value); - if (it != _quickActions.end()) { - if (it->second->ripple) { - it->second->ripple->lastStop(); - it->second->rippleFg->lastStop(); - } - if (pressed == _selected) { - PerformQuickDialogAction( - _controller, - history->peer, - it->second->action, - _filterId); - } + const auto raw = _activeQuickAction.get(); + if (raw->ripple) { + raw->ripple->lastStop(); } + if (raw->rippleFg) { + raw->rippleFg->lastStop(); + } + + if (pressed == _selected) { + PerformQuickDialogAction( + _controller, + history->peer, + raw->action, + _filterId); + } + _inactiveQuickActions.push_back( + QuickActionPtr{ _activeQuickAction.release() }); } } updateSelectedRow(); @@ -5094,7 +5122,7 @@ void InnerWidget::setSwipeContextData( return; } if (!data) { - _quickActions.remove(key); + _activeQuickAction = nullptr; return; } const auto context = ensureQuickAction(key); @@ -5125,14 +5153,17 @@ void InnerWidget::setSwipeContextData( not_null InnerWidget::ensureQuickAction(int64 key) { Expects(key != 0); - const auto it = _quickActions.find(key); - if (it == _quickActions.end()) { - return _quickActions.emplace( - key, - std::make_unique()).first->second.get(); - } else { - return it->second.get(); + if (_activeQuickAction) { + if (_activeQuickAction->data.msgBareId == key) { + return _activeQuickAction.get(); + } else { + _inactiveQuickActions.push_back( + QuickActionPtr{ _activeQuickAction.release() }); + } } + _activeQuickAction = std::make_unique(); + _activeQuickAction->data.msgBareId = key; + return _activeQuickAction.get(); } int64 InnerWidget::calcSwipeKey(int top) { @@ -5169,4 +5200,8 @@ void InnerWidget::prepareQuickAction( context->action = action; } +void InnerWidget::clearQuickActions() { + _inactiveQuickActions.clear(); +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index c3bd31867a..e57dc42f09 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -221,6 +221,7 @@ public: std::optional data); [[nodiscard]] int64 calcSwipeKey(int top); void prepareQuickAction(int64 key, Dialogs::Ui::QuickDialogAction); + void clearQuickActions(); protected: void visibleTopBottomUpdated( @@ -630,7 +631,8 @@ private: rpl::event_stream _openBotMainAppRequests; using QuickActionPtr = std::unique_ptr; - base::flat_map _quickActions; + QuickActionPtr _activeQuickAction; + std::vector _inactiveQuickActions; RowDescriptor _chatPreviewRow; bool _chatPreviewScheduled = false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index e68d3df901..fe62494c35 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -729,30 +729,33 @@ void Widget::setupSwipeBack() { const auto isRightToLeft = direction == Qt::RightToLeft; const auto action = Core::App().settings().quickDialogAction(); const auto isDisabled = action == Ui::QuickDialogAction::Disabled; - if (!isRightToLeft && _inner) { - if (const auto key = _inner->calcSwipeKey(top); - key && !isDisabled) { - _inner->prepareQuickAction(key, action); - return Ui::Controls::SwipeHandlerFinishData{ - .callback = [=, session = &session()] { - auto callback = [=, peerId = PeerId(key)] { - const auto peer = session->data().peer(peerId); - PerformQuickDialogAction( - controller(), - peer, - action, - _inner->filterId()); - }; - base::call_delayed( - st::slideWrapDuration, - session, - std::move(callback)); - }, - .msgBareId = key, - .speedRatio = 1., - .reachRatioDuration = crl::time(st::slideWrapDuration), - .provideReachOutRatio = true, - }; + if (_inner) { + _inner->clearQuickActions(); + if (!isRightToLeft) { + if (const auto key = _inner->calcSwipeKey(top); + key && !isDisabled) { + _inner->prepareQuickAction(key, action); + return Ui::Controls::SwipeHandlerFinishData{ + .callback = [=, session = &session()] { + auto callback = [=, peerId = PeerId(key)] { + PerformQuickDialogAction( + controller(), + session->data().peer(peerId), + action, + _inner->filterId()); + }; + base::call_delayed( + st::slideWrapDuration, + session, + std::move(callback)); + }, + .msgBareId = key, + .speedRatio = 1., + .reachRatioDuration = crl::time( + st::slideWrapDuration), + .provideReachOutRatio = true, + }; + } } } if (controller()->openedFolder().current()) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index c559693051..dd8155f16b 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -898,15 +898,17 @@ void PaintRow( p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r); } const auto quickWidth = st::dialogsQuickActionSize * 3; - DrawQuickAction( - p, - QRect( - rect::right(geometry) - quickWidth, - geometry.y(), - quickWidth, - geometry.height()), - context.quickActionContext->icon.get(), - labelType); + if (context.quickActionContext->icon) { + DrawQuickAction( + p, + QRect( + rect::right(geometry) - quickWidth, + geometry.y(), + quickWidth, + geometry.height()), + context.quickActionContext->icon.get(), + labelType); + } p.setClipping(false); } if (const auto quick = context.quickActionContext; From 838711e2ac827acd333e26713e9d7c93c95b4a17 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 17:26:32 +0300 Subject: [PATCH 081/122] Added maximum of lifetime to every deactivated quick dialog action. --- .../dialogs/dialogs_inner_widget.cpp | 19 +++++++++++++------ .../dialogs/dialogs_inner_widget.h | 1 + .../dialogs/ui/dialogs_quick_action_context.h | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index cd8def62ea..111c226809 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -848,8 +848,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) { auto it = _inactiveQuickActions.begin(); while (it != _inactiveQuickActions.end()) { const auto raw = it->get(); - if ((!raw->ripple || raw->ripple->empty()) - && (!raw->rippleFg || raw->rippleFg->empty())) { + if (raw->finishedAt + && (ms - raw->finishedAt + > st::defaultRippleAnimation.hideDuration)) { _inactiveQuickActions.erase(it); } else { if (raw->data.msgBareId == history->peer->id.value) { @@ -2347,8 +2348,7 @@ void InnerWidget::mousePressReleased( raw->action, _filterId); } - _inactiveQuickActions.push_back( - QuickActionPtr{ _activeQuickAction.release() }); + deactivateQuickAction(); } } updateSelectedRow(); @@ -5157,8 +5157,7 @@ not_null InnerWidget::ensureQuickAction(int64 key) { if (_activeQuickAction->data.msgBareId == key) { return _activeQuickAction.get(); } else { - _inactiveQuickActions.push_back( - QuickActionPtr{ _activeQuickAction.release() }); + deactivateQuickAction(); } } _activeQuickAction = std::make_unique(); @@ -5204,4 +5203,12 @@ void InnerWidget::clearQuickActions() { _inactiveQuickActions.clear(); } +void InnerWidget::deactivateQuickAction() { + if (_activeQuickAction) { + _activeQuickAction->finishedAt = crl::now(); + _inactiveQuickActions.push_back( + QuickActionPtr{ _activeQuickAction.release() }); + } +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index e57dc42f09..44d1f8d497 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -486,6 +486,7 @@ private: [[nodiscard]] not_null ensureQuickAction( int64 key); + void deactivateQuickAction(); [[nodiscard]] bool lookupIsInBotAppButton( Row *row, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h index d60b6974f5..a1baa28730 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_quick_action_context.h @@ -41,6 +41,7 @@ struct QuickActionContext { std::unique_ptr ripple; std::unique_ptr rippleFg; QuickDialogAction action; + crl::time finishedAt = 0; }; } // namespace Dialogs::Ui From bdf8a37a8f0f484eb5370bcaedf546f42c88a0e2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 17:55:32 +0300 Subject: [PATCH 082/122] Guarded repaint of special quick dialog actions that lead to de-listing. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 111c226809..e2aa2c5a43 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1987,11 +1987,15 @@ bool InnerWidget::addQuickActionRipple( const auto size = QSize( st::dialogsQuickActionRippleSize, row->height()); + const auto isRemovingFromList + = (action == Dialogs::Ui::QuickDialogAction::Archive); if (!context->ripple) { context->ripple = std::make_unique( st::defaultRippleAnimation, Ui::RippleAnimation::RectMask(size), - updateCallback); + isRemovingFromList + ? Fn([=] { update(); }) + : updateCallback); } if (!context->rippleFg) { context->rippleFg = std::make_unique( @@ -2012,7 +2016,9 @@ bool InnerWidget::addQuickActionRipple( action, _filterId)); }), - std::move(updateCallback)); + isRemovingFromList + ? Fn([=] { update(); }) + : std::move(updateCallback)); } context->ripple->add(QPoint(size.width() / 2, size.height() / 2)); context->rippleFg->add(QPoint(size.width() / 2, size.height() / 2)); From 28c125a3ec55d5d5054f44b26786869f8044cb44 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 17:59:17 +0300 Subject: [PATCH 083/122] Added some special cases when quick dialog action can't be performed. --- .../dialogs/dialogs_quick_action.cpp | 12 +++++++++++- .../SourceFiles/window/window_peer_menu.cpp | 17 ++++++++++++----- Telegram/SourceFiles/window/window_peer_menu.h | 1 + 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index e1092d5c9d..8e546b0d30 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -125,6 +125,9 @@ Ui::QuickDialogActionLabel ResolveQuickDialogLabel( Ui::QuickDialogAction action, FilterId filterId) { if (action == Dialogs::Ui::QuickDialogAction::Mute) { + if (history->peer->isSelf()) { + return Ui::QuickDialogActionLabel::Disabled; + } const auto isMuted = rpl::variable( MuteMenu::ThreadDescriptor(history).isMutedValue()).current(); return isMuted @@ -136,10 +139,17 @@ Ui::QuickDialogActionLabel ResolveQuickDialogLabel( ? Ui::QuickDialogActionLabel::Unpin : Ui::QuickDialogActionLabel::Pin; } else if (action == Dialogs::Ui::QuickDialogAction::Read) { - return Window::IsUnreadThread(history) + const auto unread = Window::IsUnreadThread(history); + if (history->isForum() && !unread) { + return Ui::QuickDialogActionLabel::Disabled; + } + return unread ? Ui::QuickDialogActionLabel::Read : Ui::QuickDialogActionLabel::Unread; } else if (action == Dialogs::Ui::QuickDialogAction::Archive) { + if (!Window::CanArchive(history, history->peer)) { + return Ui::QuickDialogActionLabel::Disabled; + } return Window::IsArchived(history) ? Ui::QuickDialogActionLabel::Unarchive : Ui::QuickDialogActionLabel::Archive; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 9768688eed..6fbf2c3eb0 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -684,12 +684,8 @@ void Filler::addToggleArchive() { } const auto peer = _peer; const auto history = _request.key.history(); - if (history && history->useTopPromotion()) { + if (!CanArchive(history, peer)) { return; - } else if (peer->isNotificationsUser() || peer->isSelf()) { - if (!history || !history->folder()) { - return; - } } const auto isArchived = [=] { return IsArchived(history); @@ -3320,4 +3316,15 @@ bool IsArchived(not_null history) { return (history->folder() != nullptr); } +bool CanArchive(History *history, PeerData *peer) { + if (history && history->useTopPromotion()) { + return false; + } else if (peer && (peer->isNotificationsUser() || peer->isSelf())) { + if (!history || !history->folder()) { + return false; + } + } + return true; +} + } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index e3adf079bf..5ad0c50813 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -216,5 +216,6 @@ void MarkAsReadThread(not_null thread); void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction); [[nodiscard]] bool IsArchived(not_null history); +[[nodiscard]] bool CanArchive(History *history, PeerData *peer); } // namespace Window From 2be76760de1adccbf827d68df759d349575f16aa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Mar 2025 21:01:31 +0300 Subject: [PATCH 084/122] Slightly improved style of settings for quick dialog action. --- Telegram/Resources/langs/lang.strings | 2 + .../dialogs/dialogs_quick_action.cpp | 26 ++- .../dialogs/dialogs_quick_action.h | 4 +- Telegram/SourceFiles/settings/settings.style | 2 + .../SourceFiles/settings/settings_chat.cpp | 177 +++++++++++++++++- 5 files changed, 199 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f21eeed320..dae3297d30 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1182,6 +1182,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_quick_dialog_action_title" = "Chat list quick action"; "lng_settings_quick_dialog_action_about" = "Choose the action you want to perform when you middle-click or swipe left in the chat list."; +"lng_settings_quick_dialog_action_both" = "Swipe left and Middle-click"; +"lng_settings_quick_dialog_action_swipe" = "Swipe left"; "lng_settings_quick_dialog_action_mute" = "Mute"; "lng_settings_quick_dialog_action_unmute" = "Unmute"; "lng_settings_quick_dialog_action_pin" = "Pin"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index 8e546b0d30..340a9f3b6e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -213,8 +213,10 @@ void DrawQuickAction( QPainter &p, const QRect &rect, not_null icon, - Ui::QuickDialogActionLabel label) { - const auto iconSize = st::dialogsQuickActionSize; + Ui::QuickDialogActionLabel label, + float64 iconRatio, + bool twoLines) { + const auto iconSize = st::dialogsQuickActionSize * iconRatio; const auto innerHeight = iconSize * 2; const auto top = (rect.height() - innerHeight) / 2; icon->paint(p, rect.x() + (rect.width() - iconSize) / 2, top); @@ -222,10 +224,22 @@ void DrawQuickAction( p.setBrush(Qt::NoBrush); const auto availableWidth = rect.width(); p.setFont(SwipeActionFont(label, availableWidth)); - p.drawText( - QRect(rect.x(), top, availableWidth, innerHeight), - ResolveQuickDialogLabel(label), - style::al_bottom); + if (twoLines) { + auto text = ResolveQuickDialogLabel(label); + const auto index = text.indexOf(' '); + if (index != -1) { + text = text.replace(index, 1, '\n'); + } + p.drawText( + QRect(rect.x(), top, availableWidth, innerHeight), + std::move(text), + style::al_bottom); + } else { + p.drawText( + QRect(rect.x(), top, availableWidth, innerHeight), + ResolveQuickDialogLabel(label), + style::al_bottom); + } } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.h b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h index e8d53c06b7..4ba26f291d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.h +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.h @@ -50,6 +50,8 @@ void DrawQuickAction( QPainter &p, const QRect &rect, not_null icon, - Ui::QuickDialogActionLabel label); + Ui::QuickDialogActionLabel label, + float64 iconRatio = 1., + bool twoLines = false); } // namespace Dialogs diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index a64cc6e92a..b866ec905a 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -682,3 +682,5 @@ settingsChatLinkField: InputField(defaultInputField) { style: defaultTextStyle; } + +settingsQuickDialogActionsTriggerFont: font(11px); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 89399c0370..d375700ee1 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/background_preview_box.h" #include "boxes/download_path_box.h" #include "boxes/local_storage_box.h" +#include "dialogs/ui/dialogs_quick_action_context.h" +#include "dialogs/dialogs_quick_action.h" #include "ui/boxes/choose_font_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" @@ -39,11 +41,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/image/image.h" #include "ui/painter.h" +#include "ui/rect.h" #include "ui/vertical_list.h" #include "ui/ui_utility.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "history/view/history_view_quick_action.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "export/export_manager.h" #include "window/themes/window_theme.h" #include "window/themes/window_themes_embedded.h" @@ -76,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_window.h" +#include "styles/style_dialogs.h" namespace Settings { namespace { @@ -1272,7 +1277,7 @@ void SetupChatBackground( }, adaptive->lifetime()); } -void SetupChatListSwipe( +void SetupChatListQuickAction( not_null controller, not_null container) { Ui::AddDivider(container); @@ -1282,13 +1287,141 @@ void SetupChatListSwipe( tr::lng_settings_quick_dialog_action_title()); using Type = Dialogs::Ui::QuickDialogAction; + using LabelType = Dialogs::Ui::QuickDialogActionLabel; const auto group = std::make_shared>( Core::App().settings().quickDialogAction()); group->setChangedCallback([=](Type value) { Core::App().settings().setQuickDialogAction(value); Core::App().saveSettings(); }); - container->add( + + const auto actionToLabel = [](Type value) { + switch (value) { + case Type::Mute: return LabelType::Mute; + case Type::Pin: return LabelType::Pin; + case Type::Read: return LabelType::Read; + case Type::Archive: return LabelType::Archive; + case Type::Delete: return LabelType::Delete; + default: return LabelType::Disabled; + } + }; + static constexpr auto kDisabledIconRatio = 1.25; + + const auto addPreview = [=](not_null container) { + const auto widget = container->add( + object_ptr(container)); + widget->resize(0, st::dialogsRowHeight); + struct State { + std::unique_ptr icon; + }; + const auto state = widget->lifetime().make_state(); + group->value() | rpl::start_with_next([=](Type value) { + const auto label = actionToLabel(value); + state->icon = Lottie::MakeIcon({ + .name = Dialogs::ResolveQuickDialogLottieIconName(label), + .sizeOverride = Size((label == LabelType::Disabled) + ? int(st::dialogsQuickActionSize * kDisabledIconRatio) + : st::dialogsQuickActionSize), + }); + state->icon->animate( + [=] { widget->update(); }, + 0, + state->icon->framesCount() - 1); + widget->update(); + }, widget->lifetime()); + widget->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(widget); + + const auto height = st::dialogsRowHeight; + const auto actionWidth = st::dialogsQuickActionRippleSize * 0.75; + const auto rightOffset = st::dialogsQuickActionRippleSize + + st::dialogsQuickActionSize; + const auto rect = QRect( + widget->width() + - actionWidth + - st::boxRowPadding.right() + - rightOffset, + 0, + actionWidth, + height); + + auto path = QPainterPath(); + path.addRoundedRect( + QRect( + -actionWidth, + 0, + rect::right(rect) + actionWidth, + height), + st::roundRadiusLarge, + st::roundRadiusLarge); + p.setClipPath(path); + + const auto label = actionToLabel(group->current()); + const auto isDisabled = (label == LabelType::Disabled); + + auto hq = PainterHighQualityEnabler(p); + p.fillRect( + QRect(0, 0, rect::right(rect), st::lineWidth), + st::windowBgOver); + p.fillRect( + QRect( + 0, + rect::bottom(rect) - st::lineWidth, + rect::right(rect), + st::lineWidth), + st::windowBgOver); + p.fillRect(rect, Dialogs::ResolveQuickActionBg(label)); + if (state->icon) { + Dialogs::DrawQuickAction( + p, + rect, + state->icon.get(), + label, + isDisabled ? kDisabledIconRatio : 1., + isDisabled); + } + p.translate(-height / 2, 0); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgOver); + p.drawEllipse(Rect(Size(height)) - Margins(height / 6)); + + const auto h = st::normalFont->ascent / 1.5; + p.drawRoundedRect( + height, + height / 2 - h * 1.5, + st::dialogsQuickActionRippleSize * 0.6, + h, + h / 2, + h / 2); + p.drawRoundedRect( + height, + height / 2 + h, + st::dialogsQuickActionRippleSize * 1.0, + h, + h / 2, + h / 2); + + p.setClipping(false); + p.resetTransform(); + p.setFont(st::settingsQuickDialogActionsTriggerFont); + p.setPen(st::windowSubTextFg); + p.drawText( + QRect( + widget->width() + - st::dialogsQuickActionRippleSize + - st::boxRowPadding.right(), + 0, + st::dialogsQuickActionRippleSize, + height), + isDisabled + ? tr::lng_settings_quick_dialog_action_swipe(tr::now) + : tr::lng_settings_quick_dialog_action_both(tr::now), + style::al_center); + }, widget->lifetime()); + }; + + const auto &st = st::settingsButton; + const auto button = container->add( object_ptr( container, group->value() | rpl::map([](Type value) { @@ -1302,8 +1435,39 @@ void SetupChatListSwipe( ? tr::lng_settings_quick_dialog_action_archive : tr::lng_settings_quick_dialog_action_disabled)(); }) | rpl::flatten_latest(), - st::settingsButtonNoIcon) - )->setClickedCallback([=] { + st)); + + { + const auto icon = button->lifetime().make_state(button); + icon->setAttribute(Qt::WA_TransparentForMouseEvents); + icon->resize(st::menuIconArchive.size()); + icon->show(); + button->sizeValue( + ) | rpl::start_with_next([=, left = st.iconLeft](QSize size) { + icon->moveToLeft( + left, + (size.height() - icon->height()) / 2, + size.width()); + }, icon->lifetime()); + icon->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(icon); + const auto value = group->current(); + ((value == Dialogs::Ui::QuickDialogAction::Mute) + ? st::menuIconMute + : (value == Dialogs::Ui::QuickDialogAction::Pin) + ? st::menuIconPin + : (value == Dialogs::Ui::QuickDialogAction::Read) + ? st::menuIconMarkRead + : (value == Dialogs::Ui::QuickDialogAction::Delete) + ? st::menuIconDelete + : (value == Dialogs::Ui::QuickDialogAction::Archive) + ? st::menuIconArchive + : st::menuIconShowInFolder).paintInCenter(p, icon->rect()); + }, icon->lifetime()); + } + + button->setClickedCallback([=] { controller->uiShow()->showBox(Box([=](not_null box) { box->setTitle(tr::lng_settings_quick_dialog_action_title()); const auto addRadio = [&](Type value, tr::phrase<> phrase) { @@ -1316,6 +1480,9 @@ void SetupChatListSwipe( st::settingsSendType), st::settingsSendTypePadding); }; + addPreview(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); addRadio(Type::Mute, tr::lng_settings_quick_dialog_action_mute); addRadio(Type::Pin, tr::lng_settings_quick_dialog_action_pin); addRadio(Type::Read, tr::lng_settings_quick_dialog_action_read); @@ -1868,7 +2035,7 @@ void Chat::setupContent(not_null controller) { SetupThemeSettings(controller, content); SetupCloudThemes(controller, content); SetupChatBackground(controller, content); - SetupChatListSwipe(controller, content); + SetupChatListQuickAction(controller, content); SetupStickersEmoji(controller, content); SetupMessages(controller, content); Ui::AddDivider(content); From 5adab2739badcfa29761df25c51d8ea807b85d9d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Mar 2025 20:02:57 +0300 Subject: [PATCH 085/122] Fixed icon scale in call panel when recipient has low level of battery. --- Telegram/SourceFiles/calls/calls_panel.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 673216650d..e5af06c171 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -715,18 +715,21 @@ void Panel::createRemoteLowBattery() { }, _remoteLowBattery->lifetime()); constexpr auto kBatterySize = QSize(29, 13); + const auto scaledBatterySize = QSize( + style::ConvertScale(kBatterySize.width()), + style::ConvertScale(kBatterySize.height())); const auto icon = [&] { auto svg = QSvgRenderer( BatterySvg(kBatterySize, st::videoPlayIconFg->c)); auto image = QImage( - kBatterySize * style::DevicePixelRatio(), + scaledBatterySize * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(style::DevicePixelRatio()); image.fill(Qt::transparent); { auto p = QPainter(&image); - svg.render(&p, Rect(kBatterySize)); + svg.render(&p, Rect(scaledBatterySize)); } return image; }(); @@ -745,7 +748,7 @@ void Panel::createRemoteLowBattery() { p.drawImage( st::callTooltipMutedIconPosition.x(), - (r.height() - kBatterySize.height()) / 2, + (r.height() - scaledBatterySize.height()) / 2, icon); }, _remoteLowBattery->lifetime()); From 1f07e7978de8706408e49e8026452980ae859a87 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Mar 2025 20:24:51 +0300 Subject: [PATCH 086/122] Improved background buttons in box for share QR box on different scales. --- Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp b/Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp index d7e853bd7e..e49e457454 100644 --- a/Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp @@ -592,20 +592,25 @@ void FillPeerQrBox( spacing + ((counter % kMaxInRow) * (size + spacing)), 0); widget->show(); + + const auto cornersMask = Images::CornersMask( + st::roundRadiusLarge * style::DevicePixelRatio()); const auto back = [&] { - auto result = Images::Round( - Images::GenerateGradient( - Size(size - activewidth * 5), - colors, - 0, - 0), - ImageRoundRadius::Large); + auto gradient = Images::GenerateGradient( + Size(size - activewidth * 5) * style::DevicePixelRatio(), + colors, + 0, + 0); + gradient.setDevicePixelRatio(style::DevicePixelRatio()); + auto result = Images::Round(std::move(gradient), cornersMask); + const auto rect = Rect( + result.size() / style::DevicePixelRatio()); auto colored = result; colored.fill(Qt::transparent); { auto p = QPainter(&colored); auto hq = PainterHighQualityEnabler(p); - st::profileQrIcon.paintInCenter(p, result.rect()); + st::profileQrIcon.paintInCenter(p, rect); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.drawImage(0, 0, result); } @@ -617,8 +622,8 @@ void FillPeerQrBox( p.setPen(st::premiumButtonFg); p.setBrush(st::premiumButtonFg); const auto size = st::profileQrIcon.width() * 1.5; - const auto margins = Margins((result.width() - size) / 2); - const auto inner = result.rect() - margins; + const auto margins = Margins((rect.width() - size) / 2); + const auto inner = rect - margins; p.drawRoundedRect( inner, st::roundRadiusLarge, From 7cf832a2f43d2d1f107179aefdf224849ebc2d99 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 23 Mar 2025 13:55:53 +0300 Subject: [PATCH 087/122] Replaced div tags with span for reactions in export chat history. --- .../SourceFiles/export/output/export_output_html.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index eda916d0a2..650dc0ebf1 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1560,7 +1560,9 @@ auto HtmlWriter::Wrap::pushMessage( block.append(popTag()); } if (!message.reactions.empty()) { - block.append(pushDiv("reactions")); + block.append(pushTag("span", { + { "class", "reactions" }, + })); for (const auto &reaction : message.reactions) { auto reactionClass = QByteArray("reaction"); for (const auto &recent : reaction.recent) { @@ -1574,10 +1576,10 @@ auto HtmlWriter::Wrap::pushMessage( reactionClass += " paid"; } - block.append(pushTag("div", { + block.append(pushTag("span", { { "class", reactionClass }, })); - block.append(pushTag("div", { + block.append(pushTag("span", { { "class", "emoji" }, })); switch (reaction.type) { @@ -1596,7 +1598,7 @@ auto HtmlWriter::Wrap::pushMessage( } block.append(popTag()); if (!reaction.recent.empty()) { - block.append(pushTag("div", { + block.append(pushTag("span", { { "class", "userpics" }, })); for (const auto &recent : reaction.recent) { @@ -1617,7 +1619,7 @@ auto HtmlWriter::Wrap::pushMessage( } if (reaction.recent.empty() || (reaction.count > reaction.recent.size())) { - block.append(pushTag("div", { + block.append(pushTag("span", { { "class", "count" }, })); block.append(NumberToString(reaction.count)); From c4b37950a9031ff1e4bb4f005a338c5807bfa43b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Mar 2025 12:57:48 +0300 Subject: [PATCH 088/122] Added filename as fallback for audio files while export chat history. Fixed #28920. --- .../SourceFiles/export/output/export_output_html.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 650dc0ebf1..c2c32f2ffd 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -2323,10 +2323,12 @@ MediaData HtmlWriter::Wrap::prepareMediaData( } else if (data.isVideoFile) { // At least try to pushVideoFileMedia. } else if (data.isAudioFile) { - result.title = (data.songPerformer.isEmpty() - || data.songTitle.isEmpty()) - ? QByteArray("Audio file") - : data.songPerformer + " \xe2\x80\x93 " + data.songTitle; + result.title = (!data.songPerformer.isEmpty() + && !data.songTitle.isEmpty()) + ? (data.songPerformer + " \xe2\x80\x93 " + data.songTitle) + : !data.name.isEmpty() + ? data.name + : QByteArray("Audio file"); result.status = FormatDuration(data.duration); if (!hasFile) { result.status += ", " + FormatFileSize(data.file.size); From a30951dc91124e4c79ecbf000cada91ec674a707 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Mar 2025 15:34:54 +0300 Subject: [PATCH 089/122] Fixed swipe handler for small chat lists. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index fe62494c35..ea75281db7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -379,6 +379,10 @@ Widget::Widget( _childListPeerId.value(), _childListShown.value(), makeChildListShown))); + _scroll->heightValue() | rpl::start_with_next([=](int height) { + _inner->setMinimumHeight(height); + _inner->refresh(); + }, _inner->lifetime()); _scrollToTop->raise(); _lockUnlock->toggle(false, anim::type::instant); From f734c0475bcbb78f0c1a6b83c02f200955d535c0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Mar 2025 14:50:11 +0300 Subject: [PATCH 090/122] Replaced SetupSwipeHandler signature from arguments to struct. --- .../chat_helpers/tabbed_selector.cpp | 18 +++++++++++---- .../SourceFiles/dialogs/dialogs_widget.cpp | 16 +++++++++++--- .../admin_log/history_admin_log_section.cpp | 14 +++++++++--- .../history/history_inner_widget.cpp | 18 +++++++++++---- .../history_view_swipe_back_session.cpp | 15 +++++++++---- .../view/history_view_replies_section.cpp | 18 +++++++++++---- .../SourceFiles/info/info_content_widget.cpp | 14 +++++++++--- .../SourceFiles/ui/controls/swipe_handler.cpp | 22 +++++++++---------- .../SourceFiles/ui/controls/swipe_handler.h | 17 ++++++++------ .../SourceFiles/window/window_main_menu.cpp | 14 +++++++++--- 10 files changed, 120 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 3b73b3d8b0..04d49849d2 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -533,8 +533,7 @@ TabbedSelector::~TabbedSelector() = default; void TabbedSelector::reinstallSwipe(not_null widget) { _swipeLifetime.destroy(); - Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=]( - Ui::Controls::SwipeContextData data) { + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation != 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -552,7 +551,9 @@ void TabbedSelector::reinstallSwipe(not_null widget) { } else if (_swipeBackData.lifetime) { _swipeBackData = {}; } - }, [=](int, Qt::LayoutDirection direction) { + }; + + auto init = [=](int, Qt::LayoutDirection direction) { if (!_tabsSlider) { return Ui::Controls::SwipeHandlerFinishData(); } @@ -571,7 +572,16 @@ void TabbedSelector::reinstallSwipe(not_null widget) { }); } return Ui::Controls::SwipeHandlerFinishData(); - }, nullptr, &_swipeLifetime); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = widget, + .scroll = _scroll.data(), + .update = std::move(update), + .init = std::move(init), + .dontStart = nullptr, + .onLifetime = &_swipeLifetime, + }); } const style::EmojiPan &TabbedSelector::st() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index ea75281db7..a75b229fe1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -691,8 +691,8 @@ void Widget::setupSwipeBack() { } return !current; }; - Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=]( - Ui::Controls::SwipeContextData data) { + + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation != 0) { if (data.translation < 0 && _inner @@ -724,7 +724,9 @@ void Widget::setupSwipeBack() { _inner->update(); } } - }, [=](int top, Qt::LayoutDirection direction) { + }; + + auto init = [=](int top, Qt::LayoutDirection direction) { _swipeBackIconMirrored = false; _swipeBackMirrored = false; if (_childListShown.current()) { @@ -812,7 +814,15 @@ void Widget::setupSwipeBack() { }); } } + return Ui::Controls::SwipeHandlerFinishData(); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = _inner, + .scroll = _scroll.data(), + .update = std::move(update), + .init = std::move(init), }); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index ef88f0b384..56fce9db19 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -420,8 +420,7 @@ void Widget::setupShortcuts() { } void Widget::setupSwipeReply() { - Ui::Controls::SetupSwipeHandler(_inner.data(), _scroll.data(), [=]( - Ui::Controls::SwipeContextData data) { + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -439,13 +438,22 @@ void Widget::setupSwipeReply() { } else if (_swipeBackData.lifetime) { _swipeBackData = {}; } - }, [=](int, Qt::LayoutDirection direction) { + }; + + auto init = [=](int, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { controller()->showBackFromStack(); }); } return Ui::Controls::SwipeHandlerFinishData(); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = _inner.data(), + .scroll = _scroll.data(), + .update = std::move(update), + .init = std::move(init), }); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 643a59998c..8abce4dd04 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -528,8 +528,8 @@ void HistoryInner::setupSwipeReplyAndBack() { return; } const auto peer = _peer; - Ui::Controls::SetupSwipeHandler(this, _scroll, [=, history = _history]( - Ui::Controls::SwipeContextData data) { + + auto update = [=, history = _history](Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -559,7 +559,9 @@ void HistoryInner::setupSwipeReplyAndBack() { repaintItem(item); } } - }, [=, show = _controller->uiShow()]( + }; + + auto init = [=, show = _controller->uiShow()]( int cursorTop, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { @@ -607,7 +609,15 @@ void HistoryInner::setupSwipeReplyAndBack() { return false; }); return result; - }, _touchMaybeSelecting.value()); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = this, + .scroll = _scroll, + .update = std::move(update), + .init = std::move(init), + .dontStart = _touchMaybeSelecting.value(), + }); } bool HistoryInner::hasSelectRestriction() const { diff --git a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp index 05dfa6df6c..b757a0aad0 100644 --- a/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe_back_session.cpp @@ -21,8 +21,7 @@ void SetupSwipeBackSection( not_null list) { const auto swipeBackData = list->lifetime().make_state(); - Ui::Controls::SetupSwipeHandler(list, scroll, [=]( - Ui::Controls::SwipeContextData data) { + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!swipeBackData->callback) { const auto color = [=]() -> std::pair { @@ -43,14 +42,22 @@ void SetupSwipeBackSection( } else if (swipeBackData->lifetime) { (*swipeBackData) = {}; } - }, [=](int, Qt::LayoutDirection direction) { + }; + auto init = [=](int, Qt::LayoutDirection direction) { if (direction != Qt::RightToLeft) { return Ui::Controls::SwipeHandlerFinishData(); } return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { list->controller()->showBackFromStack(); }); - }, list->touchMaybeSelectingValue()); + }; + Ui::Controls::SetupSwipeHandler({ + .widget = list, + .scroll = scroll, + .update = std::move(update), + .init = std::move(init), + .dontStart = list->touchMaybeSelectingValue(), + }); } } // namespace Window diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 5900e2e6c1..59dd3d6c4b 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -890,8 +890,8 @@ void RepliesWidget::setupSwipeReplyAndBack() { } return false; }; - Ui::Controls::SetupSwipeHandler(_inner, _scroll.get(), [=]( - Ui::Controls::SwipeContextData data) { + + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -923,7 +923,9 @@ void RepliesWidget::setupSwipeReplyAndBack() { _history->owner().requestItemRepaint(item); } } - }, [=, show = controller()->uiShow()]( + }; + + auto init = [=, show = controller()->uiShow()]( int cursorTop, Qt::LayoutDirection direction) { if (direction == Qt::RightToLeft) { @@ -962,7 +964,15 @@ void RepliesWidget::setupSwipeReplyAndBack() { }); }; return result; - }, _inner->touchMaybeSelectingValue()); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = _inner, + .scroll = _scroll.get(), + .update = std::move(update), + .init = std::move(init), + .dontStart = _inner->touchMaybeSelectingValue(), + }); } void RepliesWidget::chooseAttach( diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index b7078a984b..34dbfd783b 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -411,8 +411,7 @@ not_null ContentWidget::scroll() const { } void ContentWidget::setupSwipeHandler(not_null widget) { - Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=]( - Ui::Controls::SwipeContextData data) { + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -429,7 +428,9 @@ void ContentWidget::setupSwipeHandler(not_null widget) { } else if (_swipeBackData.lifetime) { _swipeBackData = {}; } - }, [=](int, Qt::LayoutDirection direction) { + }; + + auto init = [=](int, Qt::LayoutDirection direction) { return (direction == Qt::RightToLeft && _controller->hasBackButton()) ? Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { checkBeforeClose(crl::guard(this, [=] { @@ -438,6 +439,13 @@ void ContentWidget::setupSwipeHandler(not_null widget) { })); }) : Ui::Controls::SwipeHandlerFinishData(); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = widget, + .scroll = _scroll.data(), + .update = std::move(update), + .init = std::move(init), }); } diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index f5c3cb60ce..36f5a34e3e 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -61,16 +61,14 @@ private: } // namespace -void SetupSwipeHandler( - not_null widget, - Scroll scroll, - Fn update, - Fn generateFinish, - rpl::producer dontStart, - rpl::lifetime *onLifetime) { +void SetupSwipeHandler(SwipeHandlerArgs &&args) { static constexpr auto kThresholdWidth = 50; static constexpr auto kMaxRatio = 1.5; + const auto widget = std::move(args.widget); + const auto scroll = std::move(args.scroll); + const auto update = std::move(args.update); + struct UpdateArgs { QPoint globalCursor; QPointF position; @@ -98,11 +96,13 @@ void SetupSwipeHandler( rpl::lifetime lifetime; }; - auto &useLifetime = onLifetime ? *onLifetime : widget->lifetime(); + auto &useLifetime = args.onLifetime + ? *(args.onLifetime) + : args.widget->lifetime(); const auto state = useLifetime.make_state(); - if (dontStart) { + if (args.dontStart) { std::move( - dontStart + args.dontStart ) | rpl::start_with_next([=](bool dontStart) { state->dontStart = dontStart; }, state->lifetime); @@ -188,7 +188,7 @@ void SetupSwipeHandler( state->data.reachRatio = value; update(state->data); }; - const auto updateWith = [=](UpdateArgs args) { + const auto updateWith = [=, generateFinish = args.init](UpdateArgs args) { const auto fillFinishByTop = [&] { if (!args.delta.x()) { LOG(("SKIPPING fillFinishByTop.")); diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.h b/Telegram/SourceFiles/ui/controls/swipe_handler.h index 6d3f796b1e..10448cea36 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.h +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.h @@ -32,13 +32,16 @@ using Scroll = std::variant< not_null, not_null>; -void SetupSwipeHandler( - not_null widget, - Scroll scroll, - Fn update, - Fn generateFinishByTop, - rpl::producer dontStart = nullptr, - rpl::lifetime *onLifetime = nullptr); +struct SwipeHandlerArgs { + not_null widget; + Scroll scroll; + Fn update; + Fn init; + rpl::producer dontStart = nullptr; + rpl::lifetime *onLifetime = nullptr; +}; + +void SetupSwipeHandler(SwipeHandlerArgs &&args); [[nodiscard]] SwipeBackResult SetupSwipeBack( not_null widget, diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index b4344d2023..9ae782dbbc 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -1051,8 +1051,7 @@ void MainMenu::setupSwipe() { }); } - Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=]( - Ui::Controls::SwipeContextData data) { + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation < 0) { if (!_swipeBackData.callback) { _swipeBackData = Ui::Controls::SetupSwipeBack( @@ -1069,13 +1068,22 @@ void MainMenu::setupSwipe() { } else if (_swipeBackData.lifetime) { _swipeBackData = {}; } - }, [=](int, Qt::LayoutDirection direction) { + }; + + auto init = [=](int, Qt::LayoutDirection direction) { if (direction != Qt::LeftToRight) { return Ui::Controls::SwipeHandlerFinishData(); } return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { closeLayer(); }); + }; + + Ui::Controls::SetupSwipeHandler({ + .widget = _inner, + .scroll = _scroll.data(), + .update = std::move(update), + .init = std::move(init), }); } From 0d085d500fc1ca176b6774f2f5f5d37ac7dfe4ac Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Mar 2025 21:42:35 +0300 Subject: [PATCH 091/122] Added ability to provide custom swipe handler to Info::ContentWidget. --- Telegram/SourceFiles/info/info_content_widget.cpp | 13 +++++++++++++ Telegram/SourceFiles/info/info_content_widget.h | 6 ++++++ Telegram/SourceFiles/info/info_wrap_widget.cpp | 5 +++++ Telegram/SourceFiles/info/info_wrap_widget.h | 5 +++++ 4 files changed, 29 insertions(+) diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 34dbfd783b..f392e132ed 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -410,7 +410,19 @@ not_null ContentWidget::scroll() const { return _scroll.data(); } +void ContentWidget::replaceSwipeHandler( + Ui::Controls::SwipeHandlerArgs *incompleteArgs) { + _swipeHandlerLifetime.destroy(); + auto args = std::move(*incompleteArgs); + args.widget = _innerWrap; + args.scroll = _scroll.data(); + args.onLifetime = &_swipeHandlerLifetime; + Ui::Controls::SetupSwipeHandler(std::move(args)); +} + void ContentWidget::setupSwipeHandler(not_null widget) { + _swipeHandlerLifetime.destroy(); + auto update = [=](Ui::Controls::SwipeContextData data) { if (data.translation > 0) { if (!_swipeBackData.callback) { @@ -446,6 +458,7 @@ void ContentWidget::setupSwipeHandler(not_null widget) { .scroll = _scroll.data(), .update = std::move(update), .init = std::move(init), + .onLifetime = &_swipeHandlerLifetime, }); } diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 84b03d2934..359f46787a 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -24,6 +24,9 @@ enum class SharedMediaType : signed char; } // namespace Storage namespace Ui { +namespace Controls { +struct SwipeHandlerArgs; +} // namespace Controls class RoundRect; class ScrollArea; class InputField; @@ -135,6 +138,8 @@ public: [[nodiscard]] virtual auto desiredBottomShadowVisibility() -> rpl::producer; + void replaceSwipeHandler(Ui::Controls::SwipeHandlerArgs *incompleteArgs); + protected: template Widget *setInnerWidget(object_ptr inner) { @@ -196,6 +201,7 @@ private: style::margins _paintPadding; Ui::Controls::SwipeBackResult _swipeBackData; + rpl::lifetime _swipeHandlerLifetime; }; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 15f98f7b8f..56bc9bf193 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -1059,6 +1059,11 @@ bool WrapWidget::willHaveBackButton( return (wrap() == Wrap::Narrow) || willHaveStack; } +void WrapWidget::replaceSwipeHandler( + Ui::Controls::SwipeHandlerArgs *incompleteArgs) { + _content->replaceSwipeHandler(std::move(incompleteArgs)); +} + WrapWidget::~WrapWidget() = default; } // namespace Info diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index fe0c4ccd28..40d4459d44 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -15,6 +15,9 @@ enum class SharedMediaType : signed char; } // namespace Storage namespace Ui { +namespace Controls { +struct SwipeHandlerArgs; +} // namespace Controls class FadeShadow; class PlainShadow; class PopupMenu; @@ -140,6 +143,8 @@ public: return _removeRequests.events(); } + void replaceSwipeHandler(Ui::Controls::SwipeHandlerArgs *incompleteArgs); + ~WrapWidget(); protected: From 4e4c69993c9308ff7ea97b0735444bc1064bfce1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Mar 2025 22:19:15 +0300 Subject: [PATCH 092/122] Ensured proper cleanup of allocated event filters for swipe handler. --- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index 36f5a34e3e..f3d99f7c3d 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -76,7 +76,7 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { bool touch = false; }; struct State { - base::unique_qptr filter; + base::unique_qptr filterContext; Ui::Animations::Simple animationReach; Ui::Animations::Simple animationEnd; SwipeContextData data; @@ -376,8 +376,8 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { return base::EventFilterResult::Continue; }; widget->setAttribute(Qt::WA_AcceptTouchEvents); - state->filter = base::make_unique_q( - base::install_event_filter(widget, filter)); + state->filterContext = base::make_unique_q(nullptr); + base::install_event_filter(state->filterContext.get(), widget, filter); } SwipeBackResult SetupSwipeBack( From 9a439e1941592c8b2ed253bdf5a26b1aa8a3fb51 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Mar 2025 22:22:48 +0300 Subject: [PATCH 093/122] Added ability to switch tabs in dialogs suggestions with swipe. --- .../dialogs/ui/dialogs_suggestions.cpp | 68 +++++++++++++++++++ .../dialogs/ui/dialogs_suggestions.h | 10 +++ 2 files changed, 78 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index d13f336f93..28511ebe55 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "storage/storage_shared_media.h" #include "ui/boxes/confirm_box.h" +#include "ui/controls/swipe_handler.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" @@ -1552,6 +1553,61 @@ void Suggestions::setupApps() { }); } +Ui::Controls::SwipeHandlerArgs Suggestions::generateIncompleteSwipeArgs() { + _swipeLifetime.destroy(); + + auto update = [=](Ui::Controls::SwipeContextData data) { + if (data.translation != 0) { + if (!_swipeBackData.callback) { + _swipeBackData = Ui::Controls::SetupSwipeBack( + this, + [=]() -> std::pair { + return { + st::historyForwardChooseBg->c, + st::historyForwardChooseFg->c, + }; + }, + data.translation < 0); + } + _swipeBackData.callback(data); + return; + } else if (_swipeBackData.lifetime) { + _swipeBackData = {}; + } + }; + auto init = [=](int, Qt::LayoutDirection direction) { + if (!_tabs) { + return Ui::Controls::SwipeHandlerFinishData(); + } + const auto activeSection = _tabs->activeSection(); + const auto isToLeft = direction == Qt::RightToLeft; + if ((isToLeft && activeSection > 0) + || (!isToLeft && activeSection < _tabKeys.size() - 1)) { + return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { + if (_tabs + && _tabs->activeSection() == activeSection) { + _swipeBackData = {}; + _tabs->setActiveSection(isToLeft + ? activeSection - 1 + : activeSection + 1); + } + }); + } + return Ui::Controls::SwipeHandlerFinishData(); + }; + return { .widget = this, .update = update, .init = init }; +} + +void Suggestions::reinstallSwipe(not_null scroll) { + _swipeLifetime.destroy(); + + auto args = generateIncompleteSwipeArgs(); + args.scroll = scroll; + args.onLifetime = &_swipeLifetime; + + Ui::Controls::SetupSwipeHandler(std::move(args)); +} + void Suggestions::selectJump(Qt::Key direction, int pageSize) { switch (_key.current().tab) { case Tab::Chats: selectJumpChats(direction, pageSize); return; @@ -1977,6 +2033,18 @@ void Suggestions::finishShow() { _appsScroll->setVisible(key == Key{ Tab::Apps }); for (const auto &[mediaKey, list] : _mediaLists) { list.wrap->setVisible(key == mediaKey); + if (key == mediaKey) { + _swipeLifetime.destroy(); + auto incomplete = generateIncompleteSwipeArgs(); + list.wrap->replaceSwipeHandler(&incomplete); + } + } + if (key == Key{ Tab::Chats }) { + reinstallSwipe(_chatsScroll.get()); + } else if (key == Key{ Tab::Channels }) { + reinstallSwipe(_channelsScroll.get()); + } else if (key == Key{ Tab::Apps }) { + reinstallSwipe(_appsScroll.get()); } } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 0d8d1df729..f269085f13 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" #include "base/timer.h" #include "dialogs/ui/top_peers_strip.h" +#include "ui/controls/swipe_handler_data.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" @@ -32,6 +33,9 @@ enum class SharedMediaType : signed char; } // namespace Storage namespace Ui { +namespace Controls { +struct SwipeHandlerArgs; +} // namespace Controls class BoxContent; class ScrollArea; class ElasticScroll; @@ -157,6 +161,9 @@ private: void setupChats(); void setupChannels(); void setupApps(); + void reinstallSwipe(not_null); + [[nodiscard]] auto generateIncompleteSwipeArgs() + -> Ui::Controls::SwipeHandlerArgs; void selectJumpChats(Qt::Key direction, int pageSize); void selectJumpChannels(Qt::Key direction, int pageSize); @@ -253,6 +260,9 @@ private: QPixmap _slideLeft; QPixmap _slideRight; + Ui::Controls::SwipeBackResult _swipeBackData; + rpl::lifetime _swipeLifetime; + int _slideLeftTop = 0; int _slideRightTop = 0; From 9f1e90d00764cccad6f94ba6b308b214d73a3fa2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 25 Mar 2025 01:03:33 +0300 Subject: [PATCH 094/122] Updated Qt to 6.2.12 on macOS. --- Telegram/build/prepare/prepare.py | 2 +- Telegram/build/qt_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index af8d0b31ba..ff66d9d53c 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout b88d491492 + git checkout 7cb9049583 """) stage('msys64', """ diff --git a/Telegram/build/qt_version.py b/Telegram/build/qt_version.py index f5a449d89f..9be6a89464 100644 --- a/Telegram/build/qt_version.py +++ b/Telegram/build/qt_version.py @@ -2,7 +2,7 @@ import sys, os def resolve(arch): if sys.platform == 'darwin': - os.environ['QT'] = '6.2.9' + os.environ['QT'] = '6.2.12' elif sys.platform == 'win32': if arch == 'arm' or 'qt6' in sys.argv: os.environ['QT'] = '6.8.2' From 7f53a19647915bd98176fc8b52405f2fe0b2b1ad Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 Mar 2025 14:54:34 +0400 Subject: [PATCH 095/122] Update API scheme to layer 201. --- Telegram/Resources/langs/lang.strings | 7 ++++ .../SourceFiles/api/api_confirm_phone.cpp | 3 ++ .../SourceFiles/api/api_global_privacy.cpp | 3 +- .../data/business/data_business_chatbots.cpp | 12 +++--- .../export/data/export_data_types.cpp | 9 ++++ .../export/data/export_data_types.h | 13 +++++- .../export/output/export_output_html.cpp | 20 +++++++++ .../export/output/export_output_json.cpp | 9 ++++ Telegram/SourceFiles/history/history_item.cpp | 41 +++++++++++++++++++ Telegram/SourceFiles/intro/intro_code.cpp | 6 +++ Telegram/SourceFiles/intro/intro_phone.cpp | 3 ++ Telegram/SourceFiles/mtproto/scheme/api.tl | 23 +++++++---- .../passport/passport_form_controller.cpp | 3 ++ 13 files changed, 137 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index dae3297d30..3d5519bdf3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2189,6 +2189,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_paid_message_some#other" = "send {count} messages"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; +"lng_action_paid_message_refund#one" = "{from} refunded {count} Star to you"; +"lng_action_paid_message_refund#other" = "{from} refunded {count} Stars to you"; +"lng_action_paid_message_refund_self#one" = "You refunded {count} Star to {name}"; +"lng_action_paid_message_refund_self#other" = "You refunded {count} Stars to {name}"; +"lng_action_message_price_free" = "Messages are now free in this group."; +"lng_action_message_price_paid#one" = "Messages now cost {count} Star each in this group."; +"lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group."; "lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#other" = "You paid {count} Stars."; diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp index 3a42a793df..a3b41fefc9 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.cpp +++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp @@ -150,6 +150,9 @@ void ConfirmPhone::resolve( }, [](const MTPDauth_sentCodeSuccess &) { LOG(("API Error: Unexpected auth.sentCodeSuccess " "(Api::ConfirmPhone).")); + }, [](const MTPDauth_sentCodePaymentRequired &) { + LOG(("API Error: Unexpected auth.sentCodePaymentRequired " + "(Api::ConfirmPhone).")); }); }).fail([=](const MTP::Error &error) { _sendRequestId = 0; diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 90c56ae4e5..01a6476c70 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -224,7 +224,8 @@ void GlobalPrivacy::update( _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( MTP_globalPrivacySettings( MTP_flags(flags), - MTP_long(newChargeStars)) + MTP_long(newChargeStars), + MTPDisallowedStarGiftsSettings()) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); diff --git a/Telegram/SourceFiles/data/business/data_business_chatbots.cpp b/Telegram/SourceFiles/data/business/data_business_chatbots.cpp index ca894acf55..7c0ac6ea91 100644 --- a/Telegram/SourceFiles/data/business/data_business_chatbots.cpp +++ b/Telegram/SourceFiles/data/business/data_business_chatbots.cpp @@ -41,7 +41,7 @@ void Chatbots::preload() { _settings = ChatbotsSettings{ .bot = _owner->session().data().user(botId), .recipients = FromMTP(_owner, bot.vrecipients()), - .repliesAllowed = bot.is_can_reply(), + .repliesAllowed = bot.vrights().data().is_reply(), }; } else { _settings.force_assign(ChatbotsSettings()); @@ -79,13 +79,13 @@ void Chatbots::save( return; } else if (was.bot || settings.bot) { using Flag = MTPaccount_UpdateConnectedBot::Flag; + using RightFlag = MTPDbusinessBotRights::Flag; const auto api = &_owner->session().api(); api->request(MTPaccount_UpdateConnectedBot( - MTP_flags(!settings.bot - ? Flag::f_deleted - : settings.repliesAllowed - ? Flag::f_can_reply - : Flag()), + MTP_flags(!settings.bot ? Flag::f_deleted : Flag::f_rights), + MTP_businessBotRights(MTP_flags(settings.repliesAllowed + ? RightFlag::f_reply + : RightFlag())), (settings.bot ? settings.bot : was.bot)->inputUser, ForBotsToMTP(settings.recipients) )).done([=](const MTPUpdates &result) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 6e1ca060b3..f4c0c788da 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1704,6 +1704,15 @@ ServiceAction ParseServiceAction( .giftId = uint64(gift.vid().v), }; }); + }, [&](const MTPDmessageActionPaidMessagesRefunded &data) { + result.content = ActionPaidMessagesRefunded{ + .messages = data.vcount().v, + .stars = int64(data.vstars().v), + }; + }, [&](const MTPDmessageActionPaidMessagesPrice &data) { + result.content = ActionPaidMessagesPrice{ + .stars = int(data.vstars().v), + }; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index d460ee196a..a7518ba056 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -662,6 +662,15 @@ struct ActionStarGift { bool limited = false; }; +struct ActionPaidMessagesRefunded { + int messages = 0; + int64 stars = 0; +}; + +struct ActionPaidMessagesPrice { + int stars = 0; +}; + struct ServiceAction { std::variant< v::null_t, @@ -707,7 +716,9 @@ struct ServiceAction { ActionPaymentRefunded, ActionGiftStars, ActionPrizeStars, - ActionStarGift> content; + ActionStarGift, + ActionPaidMessagesRefunded, + ActionPaidMessagesPrice> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index c2c32f2ffd..7aada39659 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1367,6 +1367,26 @@ auto HtmlWriter::Wrap::pushMessage( + " sent you a gift of " + QByteArray::number(data.stars) + " Telegram Stars."; + }, [&](const ActionPaidMessagesRefunded &data) { + auto result = message.out + ? ("You refunded " + + QString::number(data.stars).toUtf8() + + " Stars for " + + QString::number(data.messages).toUtf8() + + " messages to " + + peers.wrapPeerName(dialog.peerId)) + : (peers.wrapPeerName(dialog.peerId) + + " refunded " + + QString::number(data.stars).toUtf8() + + " Stars for " + + QString::number(data.messages).toUtf8() + + " messages to you"); + return result; + }, [&](const ActionPaidMessagesPrice &data) { + auto result = "Price per messages changed to " + + QString::number(data.stars).toUtf8() + + " Telegram Stars."; + return result; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index dee8d1e947..1f482db782 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -663,6 +663,15 @@ QByteArray SerializeMessage( push("is_limited", data.limited); push("is_anonymous", data.anonymous); pushBare("gift_text", SerializeText(context, data.text)); + }, [&](const ActionPaidMessagesRefunded &data) { + pushActor(); + pushAction("paid_messages_refund"); + push("messages_count", data.messages); + push("stars_count", data.stars); + }, [&](const ActionPaidMessagesPrice &data) { + pushActor(); + pushAction("paid_messages_price_change"); + push("price_stars", data.stars); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index a6870a81be..927ea907a3 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5585,6 +5585,45 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto preparePaidMessagesRefunded = [&](const MTPDmessageActionPaidMessagesRefunded &action) { + auto result = PreparedServiceText(); + if (_from->isSelf()) { + result.links.push_back(_history->peer->createOpenLink()); + result.text = tr::lng_action_paid_message_refund_self( + tr::now, + lt_count, + action.vstars().v, + lt_name, + Ui::Text::Link(_history->peer->shortName(), 1), + Ui::Text::WithEntities); + } else { + result.links.push_back(_from->createOpenLink()); + result.text = tr::lng_action_paid_message_refund( + tr::now, + lt_count, + action.vstars().v, + lt_from, + Ui::Text::Link(_from->shortName(), 1), + Ui::Text::WithEntities); + } + return result; + }; + + auto preparePaidMessagesPrice = [&](const MTPDmessageActionPaidMessagesPrice &action) { + const auto stars = action.vstars().v; + auto result = PreparedServiceText(); + result.text = stars + ? tr::lng_action_message_price_paid( + tr::now, + lt_count, + stars, + Ui::Text::WithEntities) + : tr::lng_action_message_price_free( + tr::now, + Ui::Text::WithEntities); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -5632,6 +5671,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareGiftPrize, prepareStarGift, prepareStarGiftUnique, + preparePaidMessagesRefunded, + preparePaidMessagesPrice, PrepareEmptyText, PrepareErrorText)); diff --git a/Telegram/SourceFiles/intro/intro_code.cpp b/Telegram/SourceFiles/intro/intro_code.cpp index e48e8bb59e..d26625ae95 100644 --- a/Telegram/SourceFiles/intro/intro_code.cpp +++ b/Telegram/SourceFiles/intro/intro_code.cpp @@ -293,6 +293,9 @@ void CodeWidget::callDone(const MTPauth_SentCode &result) { } }, [&](const MTPDauth_sentCodeSuccess &data) { finish(data.vauthorization()); + }, [](const MTPDauth_sentCodePaymentRequired &) { + LOG(("API Error: Unexpected auth.sentCodePaymentRequired " + "(CodeWidget::callDone).")); }); } @@ -408,6 +411,9 @@ void CodeWidget::noTelegramCodeDone(const MTPauth_SentCode &result) { updateDescText(); }, [&](const MTPDauth_sentCodeSuccess &data) { finish(data.vauthorization()); + }, [](const MTPDauth_sentCodePaymentRequired &) { + LOG(("API Error: Unexpected auth.sentCodePaymentRequired " + "(CodeWidget::noTelegramCodeDone).")); }); } diff --git a/Telegram/SourceFiles/intro/intro_phone.cpp b/Telegram/SourceFiles/intro/intro_phone.cpp index 5e82616742..ae74136c8c 100644 --- a/Telegram/SourceFiles/intro/intro_phone.cpp +++ b/Telegram/SourceFiles/intro/intro_phone.cpp @@ -245,6 +245,9 @@ void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { goNext(); }, [&](const MTPDauth_sentCodeSuccess &data) { finish(data.vauthorization()); + }, [](const MTPDauth_sentCodePaymentRequired &) { + LOG(("API Error: Unexpected auth.sentCodePaymentRequired " + "(PhoneWidget::phoneSubmitDone).")); }); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 830b263966..b8c268bed8 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -185,6 +185,8 @@ messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long c messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction; messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long = MessageAction; messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; +messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; +messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -204,6 +206,7 @@ geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radiu auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; auth.sentCodeSuccess#2390fe44 authorization:auth.Authorization = auth.SentCode; +auth.sentCodePaymentRequired#d7cef980 store_product:string phone_code_hash:string = auth.SentCode; auth.authorization#2ea2c0d4 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int future_auth_token:flags.2?bytes user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; @@ -236,7 +239,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#d2234ea0 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 wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?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 wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long = UserFull; +userFull#2f30acf1 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 wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true display_gifts_button:flags2.16?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 wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long disallowed_stargifts:flags2.15?DisallowedStarGiftsSettings = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -427,6 +430,7 @@ updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long conne updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Update; updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update; +updateSentPhoneCode#504aa18f sent_code:auth.SentCode = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1311,7 +1315,7 @@ statsGroupTopInviter#535f779d user_id:long invitations:int = StatsGroupTopInvite stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; -globalPrivacySettings#c9d8df1c flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true hide_read_marks:flags.3?true new_noncontact_peers_require_premium:flags.4?true noncontact_peers_paid_stars:flags.5?long = GlobalPrivacySettings; +globalPrivacySettings#d55f2842 flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true hide_read_marks:flags.3?true new_noncontact_peers_require_premium:flags.4?true display_gifts_button:flags.7?true noncontact_peers_paid_stars:flags.5?long disallowed_stargifts:flags.6?DisallowedStarGiftsSettings = GlobalPrivacySettings; help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; @@ -1495,6 +1499,7 @@ inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?t inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentStarsGiveaway#751f08fa flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true stars:long boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector prize_description:flags.4?string random_id:long until_date:int currency:string amount:long users:int = InputStorePaymentPurpose; +inputStorePaymentAuthCode#9bb2636d flags:# restore:flags.0?true phone_number:string phone_code_hash:string currency:string amount:long = InputStorePaymentPurpose; paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod; @@ -1753,7 +1758,7 @@ inputQuickReplyShortcutId#1190cf1 shortcut_id:int = InputQuickReplyShortcut; messages.quickReplies#c68d6695 quick_replies:Vector messages:Vector chats:Vector users:Vector = messages.QuickReplies; messages.quickRepliesNotModified#5f91eb5b = messages.QuickReplies; -connectedBot#bd068601 flags:# can_reply:flags.0?true bot_id:long recipients:BusinessBotRecipients = ConnectedBot; +connectedBot#cd64636c flags:# bot_id:long recipients:BusinessBotRecipients rights:BusinessBotRights = ConnectedBot; account.connectedBots#17d7f87b connected_bots:Vector users:Vector = account.ConnectedBots; @@ -1761,7 +1766,7 @@ messages.dialogFilters#2ad93719 flags:# tags_enabled:flags.0?true filters:Vector birthday#6c8e1e06 flags:# day:int month:int year:flags.0?int = Birthday; -botBusinessConnection#896433b4 flags:# can_reply:flags.0?true disabled:flags.1?true connection_id:string user_id:long dc_id:int date:int = BotBusinessConnection; +botBusinessConnection#8f34b2f5 flags:# disabled:flags.1?true connection_id:string user_id:long dc_id:int date:int rights:flags.2?BusinessBotRights = BotBusinessConnection; inputBusinessIntro#9c469cd flags:# title:string description:string sticker:flags.0?InputDocument = InputBusinessIntro; @@ -1945,6 +1950,10 @@ requirementToContactEmpty#50a9839 = RequirementToContact; requirementToContactPremium#e581e4e9 = RequirementToContact; requirementToContactPaidMessages#b4f67e93 stars_amount:long = RequirementToContact; +businessBotRights#a0624cf7 flags:# reply:flags.0?true read_messages:flags.1?true delete_messages:flags.2?true change_info:flags.3?true manage_stories:flags.4?true manage_gifts:flags.5?true withdraw_stars:flags.6?true = BusinessBotRights; + +disallowedStarGiftsSettings#6291c96a flags:# disallow_unlimited_stargifts:flags.0?true disallow_limited_stargifts:flags.1?true disallow_unique_stargifts:flags.2?true = DisallowedStarGiftsSettings; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2079,7 +2088,7 @@ account.updateBusinessWorkHours#4b00e066 flags:# business_work_hours:flags.0?Bus account.updateBusinessLocation#9e6b131a flags:# geo_point:flags.1?InputGeoPoint address:flags.0?string = Bool; account.updateBusinessGreetingMessage#66cdafc4 flags:# message:flags.0?InputBusinessGreetingMessage = Bool; account.updateBusinessAwayMessage#a26a7fa5 flags:# message:flags.0?InputBusinessAwayMessage = Bool; -account.updateConnectedBot#43d8521d flags:# can_reply:flags.0?true deleted:flags.1?true bot:InputUser recipients:InputBusinessBotRecipients = Updates; +account.updateConnectedBot#66a08c7e flags:# deleted:flags.1?true rights:flags.0?BusinessBotRights bot:InputUser recipients:InputBusinessBotRecipients = Updates; account.getConnectedBots#4ea4c80f = account.ConnectedBots; account.getBotBusinessConnection#76a86270 connection_id:string = Updates; account.updateBusinessIntro#a614d034 flags:# intro:flags.0?InputBusinessIntro = Bool; @@ -2506,7 +2515,6 @@ payments.getBankCardData#2e79d779 number:string = payments.BankCardData; payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates; payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; -payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; payments.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer = Vector; payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; payments.applyGiftCode#f6e26854 slug:string = Updates; @@ -2544,6 +2552,7 @@ payments.getSavedStarGift#b455a106 stargift:Vector = payment payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl; payments.toggleChatStarGiftNotifications#60eaefa1 flags:# enabled:flags.0?true peer:InputPeer = Bool; payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector = Bool; +payments.canPurchaseStore#4fdc5ea7 purpose:InputStorePaymentPurpose = Bool; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -2664,4 +2673,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 200 +// LAYER 201 diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 6f5fd49165..a82cecffbb 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -2229,6 +2229,9 @@ void FormController::startPhoneVerification(not_null value) { }, [](const MTPDauth_sentCodeSuccess &) { LOG(("API Error: Unexpected auth.sentCodeSuccess " "(FormController::startPhoneVerification).")); + }, [](const MTPDauth_sentCodePaymentRequired &) { + LOG(("API Error: Unexpected auth.sentCodePaymentRequired " + "(FormController::startPhoneVerification).")); }); }).fail([=](const MTP::Error &error) { value->verification.requestId = 0; From d9b270b47772841c2acd99cd53a4a6e5322d73a4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 Mar 2025 17:52:13 +0400 Subject: [PATCH 096/122] Initial frozen accounts support. --- .../Resources/animations/media_forbidden.tgs | 1 + Telegram/Resources/icons/menu/hourglass.png | Bin 0 -> 630 bytes .../Resources/icons/menu/hourglass@2x.png | Bin 0 -> 1200 bytes .../Resources/icons/menu/hourglass@3x.png | Bin 0 -> 1744 bytes Telegram/Resources/langs/lang.strings | 13 ++ .../Resources/qrc/telegram/animations.qrc | 1 + .../chat_helpers/chat_helpers.style | 24 ++ .../chat_helpers/message_field.cpp | 208 +++++++++++++++++- .../SourceFiles/chat_helpers/message_field.h | 36 ++- .../data/data_chat_participant_status.cpp | 13 +- .../data/data_chat_participant_status.h | 5 +- Telegram/SourceFiles/data/data_peer.cpp | 4 + Telegram/SourceFiles/data/data_peer.h | 1 + .../SourceFiles/dialogs/dialogs_widget.cpp | 50 ++++- Telegram/SourceFiles/dialogs/dialogs_widget.h | 5 + .../SourceFiles/history/history_widget.cpp | 19 +- Telegram/SourceFiles/history/history_widget.h | 2 +- .../view/controls/compose_controls_common.h | 1 + .../history_view_compose_controls.cpp | 18 +- .../view/history_view_replies_section.cpp | 12 +- .../view/history_view_scheduled_section.cpp | 15 +- Telegram/SourceFiles/main/main_app_config.cpp | 13 ++ Telegram/SourceFiles/main/main_app_config.h | 3 + Telegram/SourceFiles/main/main_session.cpp | 38 +++- Telegram/SourceFiles/main/main_session.h | 20 ++ .../media/stories/media_stories_reply.cpp | 4 +- Telegram/SourceFiles/mtproto/mtp_instance.cpp | 12 + Telegram/SourceFiles/mtproto/mtp_instance.h | 1 + Telegram/SourceFiles/ui/menu_icons.style | 1 + 29 files changed, 481 insertions(+), 39 deletions(-) create mode 100644 Telegram/Resources/animations/media_forbidden.tgs create mode 100644 Telegram/Resources/icons/menu/hourglass.png create mode 100644 Telegram/Resources/icons/menu/hourglass@2x.png create mode 100644 Telegram/Resources/icons/menu/hourglass@3x.png diff --git a/Telegram/Resources/animations/media_forbidden.tgs b/Telegram/Resources/animations/media_forbidden.tgs new file mode 100644 index 0000000000..b1846cd5db --- /dev/null +++ b/Telegram/Resources/animations/media_forbidden.tgs @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"_051_GHSTBST_OUT","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"mouth","parent":4,"sr":1,"ks":{"p":{"a":0,"k":[0.333,12.11,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,-0.528],[7.067,0],[0,0.528],[-7.067,0]],"o":[[0,0.528],[-7.067,0],[0,-0.528],[7.067,0]],"v":[[33.711,-12.993],[-3.656,-7.631],[-37.522,-9.604],[-3.656,-9.545]],"c":true}]},{"i":{"x":0.677,"y":1},"o":{"x":1,"y":0},"t":74,"s":[{"i":[[-8.771,-8.472],[15.699,0],[-13.434,18.726],[-15.699,0]],"o":[[16.228,15.674],[-15.699,0],[6.52,-9.088],[15.699,0]],"v":[[43.825,-6.263],[-1.98,-0.006],[-47.357,-3.407],[-1.871,-13.433]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":92,"s":[{"i":[[0,-0.528],[7.067,0],[0,0.528],[-7.067,0]],"o":[[0,0.528],[-7.067,0],[0,-0.528],[7.067,0]],"v":[[33.711,-12.993],[-3.656,-12.631],[-37.522,-9.604],[-3.656,-14.545]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":106,"s":[{"i":[[0,-0.528],[7.067,0],[0,0.528],[-7.067,0]],"o":[[0,0.528],[-7.067,0],[0,-0.528],[7.067,0]],"v":[[40.66,-12.018],[6.297,-12.548],[-33.768,-8.034],[6.297,-14.462]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":136,"s":[{"i":[[0,-0.528],[7.067,0],[0,0.528],[-7.067,0]],"o":[[0,0.528],[-7.067,0],[0,-0.528],[7.067,0]],"v":[[40.66,-12.018],[6.297,-12.548],[-33.768,-8.034],[6.297,-14.462]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":150,"s":[{"i":[[0,-0.528],[5.996,0],[0,0.528],[-5.996,0]],"o":[[0,0.528],[-5.996,0],[0,-0.528],[5.996,0]],"v":[[39.448,-12.413],[-1.678,-12.848],[-33.87,-9.837],[-1.678,-14.762]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":186,"s":[{"i":[[0,-0.528],[5.996,0],[0,0.528],[-5.996,0]],"o":[[0,0.528],[-5.996,0],[0,-0.528],[5.996,0]],"v":[[39.448,-12.413],[-1.678,-12.848],[-33.87,-9.837],[-1.678,-14.762]],"c":true}]},{"t":212,"s":[{"i":[[0,-8.033],[7.067,0],[0,8.033],[-7.067,0]],"o":[[0,8.033],[-7.067,0],[0,-8.033],[7.067,0]],"v":[[12.796,0],[0,14.546],[-12.796,0],[0,-14.546]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.490196079016,0.035294119269,0.035294119269,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.490196079016,0.035294119269,0.035294119269,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"beak_bl","parent":4,"sr":1,"ks":{"o":{"a":0,"k":33},"p":{"a":0,"k":[-7.949,-24.179,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,0],[-4.412,4.147]],"o":[[5.678,-4.69],[0,0]],"v":[[-7.943,6.682],[3.424,-6.971]],"c":false}]},{"i":{"x":0.677,"y":1},"o":{"x":1,"y":0},"t":74,"s":[{"i":[[0,0],[-4.403,5.549]],"o":[[6.446,-4.392],[0,0]],"v":[[-10.628,6.975],[1.455,-6.971]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":92,"s":[{"i":[[0,0],[-4.403,5.549]],"o":[[6.446,-4.392],[0,0]],"v":[[-7.674,6.721],[3.424,-6.971]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":106,"s":[{"i":[[0,0],[-4.264,5.647]],"o":[[5.641,-5.851],[0,0]],"v":[[-8.051,8.282],[3.424,-6.971]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":136,"s":[{"i":[[0,0],[-4.264,5.647]],"o":[[5.641,-5.851],[0,0]],"v":[[-8.051,8.282],[3.424,-6.971]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":150,"s":[{"i":[[0,0],[-0.373,5.044]],"o":[[5.523,-2.968],[0,0]],"v":[[-5.282,12.038],[3.424,-6.971]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":186,"s":[{"i":[[0,0],[-0.373,5.044]],"o":[[5.523,-2.968],[0,0]],"v":[[-5.282,12.038],[3.424,-6.971]],"c":false}]},{"t":212,"s":[{"i":[[0,0],[-4.412,4.147]],"o":[[1.964,-7.603],[0,0]],"v":[[-5.443,10.182],[3.424,-6.971]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":216,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[5]},{"t":240,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":216,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[100]},{"t":240,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"NULL CONTROL","parent":18,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":1.559},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[-6.826,76.052,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":194.666,"s":[18.173,76.217,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":205.334,"s":[-26.826,76.142,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":218.666,"s":[8.174,76.142,0],"to":[0,0,0],"ti":[0,0,0]},{"t":240,"s":[-6.826,76.052,0]}]},"a":{"a":0,"k":[60,60,0]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"beak","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[-8.95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":74,"s":[-1.559]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":92,"s":[-1.559]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":106,"s":[-13.63]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":136,"s":[-13.63]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":150,"s":[-8.299]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":186,"s":[-8.299]},{"t":212,"s":[-1.559]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.789,"y":0},"t":60,"s":[76.503,30.265,0],"to":[-1.303,3.086,0],"ti":[2.223,-1.998,0]},{"i":{"x":0.419,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[68.242,9.778,0],"to":[-1.808,1.625,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":74,"s":[66.953,40.91,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":92,"s":[66.953,40.91,0],"to":[0,0,0],"ti":[-30.538,1.683,0]},{"t":106,"s":[120.906,38.44,0],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":136,"s":[120.906,38.44,0],"to":[0,0,0],"ti":[38.176,-10.366,0]},{"t":150,"s":[29.143,75.951,0],"h":1},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":186,"s":[29.143,75.951,0],"to":[37.106,-3.862,0],"ti":[0,0,0]},{"t":212,"s":[66.953,40.91,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":67.5,"s":[110,90,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":76.5,"s":[90,130,100]},{"t":90,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[7.988,0.4],[16.048,-2.349],[27.593,-7.415],[-0.76,-4.01],[-8.507,-1.957],[-12.737,1.211],[-6.332,1.939],[-0.446,4.013]],"o":[[-29.32,-1.47],[-13.151,1.925],[-7.923,2.129],[0.679,3.582],[6.961,1.601],[12.334,-1.173],[8.449,-2.587],[0.684,-6.149]],"v":[[39.197,-13.901],[-6.251,-41.866],[-46.922,-6.336],[-57.964,3.906],[-44.397,12.952],[-1.355,16.043],[40.722,9.34],[53.214,-1.325]],"c":true}]},{"i":{"x":0.677,"y":1},"o":{"x":1,"y":0},"t":74,"s":[{"i":[[6.309,1.307],[16.048,-2.349],[36.607,-17.968],[-0.882,-5.709],[-5.328,-2.761],[-11.843,0.728],[-5.624,6.202],[-0.191,4.479]],"o":[[-33.997,-8.466],[-13.151,1.925],[-6.185,2.526],[0.788,5.1],[9.679,5.133],[13.616,-0.791],[4.681,-3.634],[0.292,-6.863]],"v":[[48.12,-8.237],[-6.251,-41.866],[-51.424,-6.236],[-62.689,14.421],[-53.5,27.125],[-0.365,29.968],[55.123,20.91],[62.025,8.224]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":92,"s":[{"i":[[7.988,0.4],[16.048,-2.349],[30.244,-4.765],[-0.567,-6.434],[-8.692,-0.813],[-12.737,1.211],[-6.332,1.939],[0.309,4.532]],"o":[[-29.32,-1.47],[-13.151,1.925],[-8.105,1.277],[0.507,5.748],[15.469,1.447],[12.334,-1.173],[8.449,-2.587],[-0.473,-6.944]],"v":[[39.197,-13.901],[-6.251,-41.866],[-46.922,-8.836],[-58.646,5.725],[-45.147,17.702],[-2.105,8.543],[44.722,12.09],[55.836,0.478]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":106,"s":[{"i":[[8.444,0.423],[16.048,-2.349],[30.244,-4.765],[-0.567,-6.434],[-8.692,-0.813],[-12.737,1.211],[-6.332,1.939],[4.732,3.233]],"o":[[-29.32,-1.47],[-13.151,1.925],[-8.105,1.277],[0.507,5.748],[15.469,1.447],[12.334,-1.173],[7.709,-2.361],[7.982,-3.767]],"v":[[39.197,-13.901],[-6.251,-41.866],[-46.922,-8.836],[-58.646,5.725],[-45.147,17.702],[2.145,12.293],[37.722,11.84],[43.34,0.666]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":136,"s":[{"i":[[8.444,0.423],[16.048,-2.349],[30.244,-4.765],[-0.567,-6.434],[-8.692,-0.813],[-12.737,1.211],[-6.332,1.939],[4.732,3.233]],"o":[[-29.32,-1.47],[-13.151,1.925],[-8.105,1.277],[0.507,5.748],[15.469,1.447],[12.334,-1.173],[7.709,-2.361],[7.982,-3.767]],"v":[[39.197,-13.901],[-6.251,-41.866],[-46.922,-8.836],[-58.646,5.725],[-45.147,17.702],[2.145,12.293],[37.722,11.84],[43.34,0.666]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":150,"s":[{"i":[[8.444,0.423],[16.048,-2.349],[30.244,-4.765],[-14.233,-1.737],[-8.352,-0.781],[-12.737,1.211],[-6.572,-0.817],[1.9,16.015]],"o":[[-29.32,-1.47],[-13.151,1.925],[-8.387,1.321],[-7.971,3.727],[15.469,1.447],[12.334,-1.173],[8.179,1.017],[-0.935,-7.882]],"v":[[35.422,-16.826],[-6.251,-41.866],[-35.806,-11.299],[-33.327,1.916],[-34.031,15.24],[2.145,12.293],[37.722,11.84],[56.931,-7.328]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":186,"s":[{"i":[[8.444,0.423],[16.048,-2.349],[30.244,-4.765],[-14.233,-1.737],[-8.352,-0.781],[-12.737,1.211],[-6.572,-0.817],[1.9,16.015]],"o":[[-29.32,-1.47],[-13.151,1.925],[-8.387,1.321],[-7.971,3.727],[15.469,1.447],[12.334,-1.173],[8.179,1.017],[-0.935,-7.882]],"v":[[35.422,-16.826],[-6.251,-41.866],[-35.806,-11.299],[-33.327,1.916],[-34.031,15.24],[2.145,12.293],[37.722,11.84],[56.931,-7.328]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":196,"s":[{"i":[[6.027,2.375],[16.048,-2.349],[19.954,-12.615],[-0.527,-5.248],[-5.678,-2.454],[-11.977,0.8],[-5.84,4.308],[0.609,9.716]],"o":[[-20.173,-7.491],[-13.151,1.925],[-5.495,3.434],[0.527,5.248],[10.548,4.58],[13.424,-0.848],[5.123,-1.836],[-0.033,-7.302]],"v":[[32.125,-10.211],[-6.251,-41.866],[-33.353,-4.497],[-38.305,11.207],[-30.361,23.484],[1.309,28.462],[33.416,19.473],[44.636,3.508]],"c":true}]},{"t":212,"s":[{"i":[[4.007,4.007],[16.048,-2.349],[11.351,-19.18],[-1.059,-5.302],[-3.442,-3.852],[-11.341,0.456],[-5.227,8.592],[-0.471,4.449]],"o":[[-12.525,-12.525],[-13.151,1.925],[-3.078,5.2],[0.946,4.737],[6.433,7.2],[14.335,-0.577],[2.568,-4.221],[0.721,-6.817]],"v":[[29.368,-4.68],[-6.251,-41.866],[-31.302,1.191],[-34.064,17.264],[-27.292,30.376],[0.611,41.98],[29.815,25.855],[34.357,12.568]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.811764717102,0.207843139768,0.007843137719,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.364705890417,0.121568627656,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"/_bl2","parent":9,"sr":1,"ks":{"p":{"a":0,"k":[-83.645,15.256,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.422,51.723],[16.715,33.851]],"c":false}]},{"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.081,8.657],[17.081,-8.657]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":82.133,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":92.268,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":102.4,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":112.533,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":122.666,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132.801,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":142.934,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":153.066,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":163.199,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":173.334,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":183.467,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":193.6,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":203.732,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":213.867,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":224,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":234.133,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":244.266,"s":[0]},{"t":254.400390625,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":82.133,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":92.268,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":102.4,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":112.533,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":122.666,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132.801,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":142.934,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":153.066,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":163.199,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":173.334,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":183.467,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":193.6,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":203.732,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":213.867,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":224,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":234.133,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":244.266,"s":[95]},{"t":254.400390625,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":68,"op":300,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"/_bl","parent":9,"sr":1,"ks":{"p":{"a":0,"k":[-96.129,22.065,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-48.709,69.279],[75.289,1.117]],"c":false}]},{"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-54.457,27.599],[54.457,-27.599]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":68,"op":300,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"/_sh","parent":9,"sr":1,"ks":{"o":{"a":0,"k":33},"p":{"a":0,"k":[11.345,21.386,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[133.255,-87.597],[-167.909,75.145]],"c":false}]},{"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[137.947,-69.913],[-137.947,69.913]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":68,"op":300,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"/_l","parent":9,"sr":1,"ks":{"a":{"a":0,"k":[248.263,265.461,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[130.546,-90.927],[-169.425,71.392]],"c":false}]},{"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[144.191,-73.077],[-144.191,73.077]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[262.772,294.09]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-143.63,123.931],[158.113,-36.23]],"c":false}]},{"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-144.191,73.077],[144.191,-73.077]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[233.753,236.832]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":68,"op":300,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"/","parent":32,"sr":1,"ks":{"p":{"a":0,"k":[-7.737,9.461,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[165.769,-74.392],[-166.487,103.885],[-169.202,98.025],[163.103,-79.136]],"c":true}]},{"t":80,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[158.7,-44.448],[-129.681,101.706],[-158.7,44.448],[129.681,-101.706]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.800000011921,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":68,"op":300,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"eye_bl","parent":11,"sr":1,"ks":{"p":{"a":0,"k":[-5.809,-11.517,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":84,"s":[{"i":[[0.768,-4.443],[-4.443,-0.768],[-0.768,4.443],[4.443,0.768]],"o":[[-0.768,4.443],[4.443,0.768],[0.768,-4.443],[-4.443,-0.768]],"v":[[-8.044,-1.39],[-1.39,8.044],[8.044,1.39],[1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[0.677,-0.825],[-4.428,0.387],[-0.677,0.825],[4.428,-0.387]],"o":[[-0.677,0.825],[4.428,-0.387],[0.677,-0.825],[-4.428,0.387]],"v":[[-4.559,18.359],[2.233,19.151],[11.475,16.956],[4.684,16.163]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[{"i":[[0.768,-4.443],[-4.443,-0.768],[-0.768,4.443],[4.443,0.768]],"o":[[-0.768,4.443],[4.443,0.768],[0.768,-4.443],[-4.443,-0.768]],"v":[[-8.044,-1.39],[-1.39,8.044],[8.044,1.39],[1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":128,"s":[{"i":[[0.768,-4.443],[-4.443,-0.768],[-0.768,4.443],[4.443,0.768]],"o":[[-0.768,4.443],[4.443,0.768],[0.768,-4.443],[-4.443,-0.768]],"v":[[-8.044,-1.39],[-1.39,8.044],[8.044,1.39],[1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":134,"s":[{"i":[[0.677,-0.825],[-4.428,0.387],[-0.677,0.825],[4.428,-0.387]],"o":[[-0.677,0.825],[4.428,-0.387],[0.677,-0.825],[-4.428,0.387]],"v":[[-4.559,18.359],[2.233,19.151],[11.475,16.956],[4.684,16.163]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":140,"s":[{"i":[[0.768,-4.443],[-4.443,-0.768],[-0.768,4.443],[4.443,0.768]],"o":[[-0.768,4.443],[4.443,0.768],[0.768,-4.443],[-4.443,-0.768]],"v":[[-8.044,-1.39],[-1.39,8.044],[8.044,1.39],[1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[0.768,-4.443],[-4.443,-0.768],[-0.768,4.443],[4.443,0.768]],"o":[[-0.768,4.443],[4.443,0.768],[0.768,-4.443],[-4.443,-0.768]],"v":[[-8.044,-1.39],[-1.39,8.044],[8.044,1.39],[1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[0.677,-0.825],[-4.428,0.387],[-0.677,0.825],[4.428,-0.387]],"o":[[-0.677,0.825],[4.428,-0.387],[0.677,-0.825],[-4.428,0.387]],"v":[[-4.559,18.359],[2.233,19.151],[11.475,16.956],[4.684,16.163]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[0.783,-0.725],[-4.439,-0.22],[-0.783,0.725],[4.439,0.22]],"o":[[-0.783,0.725],[4.439,0.22],[0.783,-0.725],[-4.439,-0.22]],"v":[[-4.792,11.216],[1.828,12.927],[11.284,12.012],[4.664,10.301]],"c":true}]},{"t":202,"s":[{"i":[[0.768,-4.443],[-4.443,-0.768],[-0.768,4.443],[4.443,0.768]],"o":[[-0.768,4.443],[4.443,0.768],[0.768,-4.443],[-4.443,-0.768]],"v":[[-8.044,-1.39],[-1.39,8.044],[8.044,1.39],[1.39,-8.044]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":84,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":90,"s":[0,0,0,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":96,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":128,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":134,"s":[0,0,0,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":140,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":178,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":184,"s":[0,0,0,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":196,"s":[0,0,0,1]},{"t":202,"s":[1,1,1,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"eye","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":92,"s":[66.885,-49.27,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":106,"s":[51.584,-48.916,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":136,"s":[51.584,-48.916,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":150,"s":[73.565,-49.592,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":186,"s":[73.565,-49.592,0],"to":[0,0,0],"ti":[0,0,0]},{"t":212,"s":[66.885,-49.27,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":92,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":106,"s":[80,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":136,"s":[80,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":150,"s":[100,100,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":186,"s":[100,100,100]},{"t":212,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":84,"s":[{"i":[[-2.9,-14.385],[-12.613,2.543],[2.9,14.385],[12.613,-2.543]],"o":[[2.9,14.385],[12.613,-2.543],[-2.9,-14.385],[-12.613,2.543]],"v":[[-22.837,4.604],[5.251,26.046],[22.837,-4.604],[-5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[0.328,-1.751],[-12.613,2.543],[1.876,1.044],[12.613,-2.543]],"o":[[-0.59,3.146],[12.613,-2.543],[-1.858,-1.033],[-12.613,2.543]],"v":[[-22.695,6.615],[3.352,7.793],[24.513,-2.653],[2.167,3.644]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[{"i":[[-2.9,-14.385],[-12.613,2.543],[2.9,14.385],[12.613,-2.543]],"o":[[2.9,14.385],[12.613,-2.543],[-2.9,-14.385],[-12.613,2.543]],"v":[[-22.837,4.604],[5.251,26.046],[22.837,-4.604],[-5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":128,"s":[{"i":[[-2.9,-14.385],[-12.613,2.543],[2.9,14.385],[12.613,-2.543]],"o":[[2.9,14.385],[12.613,-2.543],[-2.9,-14.385],[-12.613,2.543]],"v":[[-22.837,4.604],[5.251,26.046],[22.837,-4.604],[-5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":134,"s":[{"i":[[0.328,-1.751],[-12.613,2.543],[1.876,1.044],[12.613,-2.543]],"o":[[-0.59,3.146],[12.613,-2.543],[-1.858,-1.033],[-12.613,2.543]],"v":[[-22.695,6.615],[3.352,7.793],[24.513,-2.653],[2.167,3.644]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":140,"s":[{"i":[[-2.9,-14.385],[-12.613,2.543],[2.9,14.385],[12.613,-2.543]],"o":[[2.9,14.385],[12.613,-2.543],[-2.9,-14.385],[-12.613,2.543]],"v":[[-22.837,4.604],[5.251,26.046],[22.837,-4.604],[-5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[-2.9,-14.385],[-12.613,2.543],[2.9,14.385],[12.613,-2.543]],"o":[[2.9,14.385],[12.613,-2.543],[-2.9,-14.385],[-12.613,2.543]],"v":[[-22.837,4.604],[5.251,26.046],[22.837,-4.604],[-5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[0.328,-1.751],[-12.613,2.543],[1.876,1.044],[12.613,-2.543]],"o":[[-0.59,3.146],[12.613,-2.543],[-1.858,-1.033],[-12.613,2.543]],"v":[[-22.695,6.615],[3.352,7.793],[24.513,-2.653],[2.167,3.644]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[0.72,-1.63],[-12.844,0.763],[1.588,1.444],[13.508,-0.923]],"o":[[-1.293,2.928],[12.803,-0.761],[-1.573,-1.43],[-12.836,0.877]],"v":[[-23.438,1.208],[1.047,2.449],[23.404,-2.719],[0.841,-1.861]],"c":true}]},{"t":202,"s":[{"i":[[-2.9,-14.385],[-12.613,2.543],[2.9,14.385],[12.613,-2.543]],"o":[[2.9,14.385],[12.613,-2.543],[-2.9,-14.385],[-12.613,2.543]],"v":[[-22.837,4.604],[5.251,26.046],[22.837,-4.604],[-5.251,-26.046]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[8]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":128,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":134,"s":[8]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"t":178,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":184,"s":[8]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":196,"s":[8]},{"t":202,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"eyebr","parent":11,"sr":1,"ks":{"p":{"a":0,"k":[-2.814,-58.554,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[0,0],[-3.88,-4.065]],"o":[[4.779,0.417],[0,0]],"v":[[-2.884,25.451],[12.517,32.218]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":74,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":84,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[0,0],[-3.585,-0.049]],"o":[[3.585,0.541],[0,0]],"v":[[-4.497,33.3],[10.497,34.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":128,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":134,"s":[{"i":[[0,0],[-3.585,-0.049]],"o":[[3.585,0.541],[0,0]],"v":[[-4.497,33.3],[10.497,34.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":140,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[0,0],[-3.585,-0.049]],"o":[[3.585,0.541],[0,0]],"v":[[-4.497,33.3],[10.497,34.726]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[0,0],[-3.585,-0.049]],"o":[[3.585,0.541],[0,0]],"v":[[-4.497,33.3],[10.497,34.726]],"c":false}]},{"t":202,"s":[{"i":[[0,0],[-3.585,-0.326]],"o":[[3.585,3.585],[0,0]],"v":[[-7.497,-4.726],[7.497,4.726]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"eye_bl","parent":14,"sr":1,"ks":{"p":{"a":0,"k":[-5.603,-8.455,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":84,"s":[{"i":[[-0.768,-4.443],[4.443,-0.768],[0.768,4.443],[-4.443,0.768]],"o":[[0.768,4.443],[-4.443,0.768],[-0.768,-4.443],[4.443,-0.768]],"v":[[8.044,-1.39],[1.39,8.044],[-8.044,1.39],[-1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[-0.694,-0.715],[4.428,0.375],[0.694,0.715],[-4.428,-0.375]],"o":[[0.694,0.715],[-4.428,-0.375],[-0.694,-0.715],[4.428,0.375]],"v":[[13.043,22.967],[6.281,23.583],[-2.994,21.61],[3.768,20.993]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[{"i":[[-0.768,-4.443],[4.443,-0.768],[0.768,4.443],[-4.443,0.768]],"o":[[0.768,4.443],[-4.443,0.768],[-0.768,-4.443],[4.443,-0.768]],"v":[[8.044,-1.39],[1.39,8.044],[-8.044,1.39],[-1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":128,"s":[{"i":[[-0.768,-4.443],[4.443,-0.768],[0.768,4.443],[-4.443,0.768]],"o":[[0.768,4.443],[-4.443,0.768],[-0.768,-4.443],[4.443,-0.768]],"v":[[8.044,-1.39],[1.39,8.044],[-8.044,1.39],[-1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":134,"s":[{"i":[[-0.694,-0.715],[4.428,0.375],[0.694,0.715],[-4.428,-0.375]],"o":[[0.694,0.715],[-4.428,-0.375],[-0.694,-0.715],[4.428,0.375]],"v":[[13.043,22.967],[6.281,23.583],[-2.994,21.61],[3.768,20.993]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":140,"s":[{"i":[[-0.768,-4.443],[4.443,-0.768],[0.768,4.443],[-4.443,0.768]],"o":[[0.768,4.443],[-4.443,0.768],[-0.768,-4.443],[4.443,-0.768]],"v":[[8.044,-1.39],[1.39,8.044],[-8.044,1.39],[-1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[-0.768,-4.443],[4.443,-0.768],[0.768,4.443],[-4.443,0.768]],"o":[[0.768,4.443],[-4.443,0.768],[-0.768,-4.443],[4.443,-0.768]],"v":[[8.044,-1.39],[1.39,8.044],[-8.044,1.39],[-1.39,-8.044]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[-0.694,-0.715],[4.428,0.375],[0.694,0.715],[-4.428,-0.375]],"o":[[0.694,0.715],[-4.428,-0.375],[-0.694,-0.715],[4.428,0.375]],"v":[[13.043,22.967],[6.281,23.583],[-2.994,21.61],[3.768,20.993]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[-0.807,-0.585],[4.427,-0.393],[0.807,0.585],[-4.427,0.393]],"o":[[0.807,0.585],[-4.427,0.393],[-0.807,-0.585],[4.427,-0.393]],"v":[[13.361,18.34],[6.806,20.111],[-2.67,19.762],[3.885,17.991]],"c":true}]},{"t":202,"s":[{"i":[[-0.768,-4.443],[4.443,-0.768],[0.768,4.443],[-4.443,0.768]],"o":[[0.768,4.443],[-4.443,0.768],[-0.768,-4.443],[4.443,-0.768]],"v":[[8.044,-1.39],[1.39,8.044],[-8.044,1.39],[-1.39,-8.044]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":84,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":90,"s":[0,0,0,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":96,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":128,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":134,"s":[0,0,0,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":140,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":178,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":184,"s":[0,0,0,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":196,"s":[0,0,0,1]},{"t":202,"s":[1,1,1,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"eye","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":92,"s":[-68.757,-49.27,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":106,"s":[-79.169,-47.871,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":136,"s":[-79.169,-47.871,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":150,"s":[-56.825,-45.23,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":186,"s":[-56.825,-45.23,0],"to":[0,0,0],"ti":[0,0,0]},{"t":212,"s":[-68.757,-49.27,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":92,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":106,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":136,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":150,"s":[80,100,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":186,"s":[80,100,100]},{"t":212,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":84,"s":[{"i":[[2.9,-14.385],[12.613,2.543],[-2.9,14.385],[-12.613,-2.543]],"o":[[-2.9,14.385],[-12.613,-2.543],[2.9,-14.385],[12.613,2.543]],"v":[[22.837,4.604],[-5.251,26.046],[-22.837,-4.604],[5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[-1.173,-0.668],[13.941,0.738],[-1.066,0.879],[-14.526,-0.228]],"o":[[0.855,0.567],[-14.499,-0.768],[1.181,-0.974],[15.349,0.241]],"v":[[26.489,9.254],[1.547,17.027],[-21.84,9.877],[2.114,11.919]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[{"i":[[2.9,-14.385],[12.613,2.543],[-2.9,14.385],[-12.613,-2.543]],"o":[[-2.9,14.385],[-12.613,-2.543],[2.9,-14.385],[12.613,2.543]],"v":[[22.837,4.604],[-5.251,26.046],[-22.837,-4.604],[5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":128,"s":[{"i":[[2.9,-14.385],[12.613,2.543],[-2.9,14.385],[-12.613,-2.543]],"o":[[-2.9,14.385],[-12.613,-2.543],[2.9,-14.385],[12.613,2.543]],"v":[[22.837,4.604],[-5.251,26.046],[-22.837,-4.604],[5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":134,"s":[{"i":[[-1.173,-0.668],[13.941,0.738],[-1.066,0.879],[-14.526,-0.228]],"o":[[0.855,0.567],[-14.499,-0.768],[1.181,-0.974],[15.349,0.241]],"v":[[26.489,9.254],[1.547,17.027],[-21.84,9.877],[2.114,11.919]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":140,"s":[{"i":[[2.9,-14.385],[12.613,2.543],[-2.9,14.385],[-12.613,-2.543]],"o":[[-2.9,14.385],[-12.613,-2.543],[2.9,-14.385],[12.613,2.543]],"v":[[22.837,4.604],[-5.251,26.046],[-22.837,-4.604],[5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[2.9,-14.385],[12.613,2.543],[-2.9,14.385],[-12.613,-2.543]],"o":[[-2.9,14.385],[-12.613,-2.543],[2.9,-14.385],[12.613,2.543]],"v":[[22.837,4.604],[-5.251,26.046],[-22.837,-4.604],[5.251,-26.046]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[-1.173,-0.668],[13.941,0.738],[-1.066,0.879],[-14.526,-0.228]],"o":[[0.855,0.567],[-14.499,-0.768],[1.181,-0.974],[15.349,0.241]],"v":[[26.489,9.254],[1.547,17.027],[-21.84,9.877],[2.114,11.919]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[-1.22,-0.578],[13.956,-0.313],[-0.997,0.957],[-14.502,0.866]],"o":[[0.895,0.501],[-14.516,0.326],[1.104,-1.06],[15.324,-0.915]],"v":[[26.299,7.431],[0.816,12.526],[-21.847,11.691],[1.13,9.135]],"c":true}]},{"t":202,"s":[{"i":[[2.9,-14.385],[12.613,2.543],[-2.9,14.385],[-12.613,-2.543]],"o":[[-2.9,14.385],[-12.613,-2.543],[2.9,-14.385],[12.613,2.543]],"v":[[22.837,4.604],[-5.251,26.046],[-22.837,-4.604],[5.251,-26.046]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[8]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":128,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":134,"s":[8]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"t":178,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":184,"s":[8]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":196,"s":[8]},{"t":202,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"eyebr","parent":14,"sr":1,"ks":{"p":{"a":0,"k":[-6.516,-53.175,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[0,0],[-5.211,-1.201]],"o":[[5.825,-5.46],[0,0]],"v":[[-7.013,30.453],[11.642,22.829]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":74,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":84,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[0,0],[-2.608,0.339]],"o":[[8.801,-0.792],[0,0]],"v":[[-4.812,42.019],[11.812,40.511]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":128,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":134,"s":[{"i":[[0,0],[-2.608,0.339]],"o":[[8.801,-0.792],[0,0]],"v":[[-4.812,42.019],[11.812,40.511]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":140,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[0,0],[-2.608,0.339]],"o":[[8.801,-0.792],[0,0]],"v":[[-4.812,42.019],[11.812,40.511]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[0,0],[-2.608,0.339]],"o":[[8.801,-0.792],[0,0]],"v":[[-4.812,42.019],[11.812,40.511]],"c":false}]},{"t":202,"s":[{"i":[[0,0],[-2.608,2.934]],"o":[[8.801,-6.845],[0,0]],"v":[[-8.312,6.519],[8.312,-6.519]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"head_bl","parent":18,"sr":1,"ks":{"p":{"a":0,"k":[108.248,-5.838,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-10.353]],"o":[[10.353,14.335],[0,0]],"v":[[-6.371,-24.29],[6.371,24.29]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.988235294819,0.933333337307,0.129411771894,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":216,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[5]},{"t":240,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":216,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[100]},{"t":240,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"head_bl","parent":18,"sr":1,"ks":{"p":{"a":0,"k":[-76.374,-39.35,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[7.802,-7.63],[3.918,-22.723]],"o":[[-9.941,5.192],[-17.455,17.071],[0,0]],"v":[[29.869,-40.695],[3.168,-21.261],[-29.869,40.695]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":73}},{"n":"g","nm":"gap","v":{"a":0,"k":20}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":216,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[5]},{"t":240,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":216,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[100]},{"t":240,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"head","parent":28,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[-0.081]},{"t":74,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[-24.536,-43.019,0],"to":[0,0,0],"ti":[0,0,0]},{"t":74,"s":[-21.04,-46.185,0]}]},"a":{"a":0,"k":[16.675,97.867,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[7.055,29.915],[71.089,-16.765],[-14.987,-63.55],[-26.123,-12.949]],"o":[[17.827,-23.465],[-14.987,-63.55],[-71.089,16.765],[7.074,29.996],[0,0]],"v":[[111.229,68.451],[128.838,-13.461],[-29.62,-109.215],[-128.596,47.25],[-76.495,112.536]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.980392158031,0.564705908298,0.086274512112,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.835294127464,0.152941182256,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"wing","parent":28,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[30.551]},{"t":74,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[78.089,-46.43,0],"to":[0,0,0],"ti":[0,0,0]},{"t":74,"s":[85.442,-36.572,0]}]},"a":{"a":0,"k":[-38.5,0,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,0],[-3.186,-10.891],[31.234,-1.969]],"o":[[33.604,-18.059],[3.186,10.891],[0,0]],"v":[[-62.791,-22.947],[33.991,-4.39],[-15.725,10.233]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":74,"s":[{"i":[[0,0],[-3.186,-10.891],[32.263,-10.878]],"o":[[30.709,-3.412],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[42.907,-7.444],[-34.866,26.7]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":92,"s":[{"i":[[0,0],[-3.186,-10.891],[32.263,-10.878]],"o":[[30.709,-3.412],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[42.907,-7.444],[-34.866,26.7]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[0,0],[-3.186,-10.891],[34.926,-8.623]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[5.907,-20.444],[-10.866,28.2]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":110,"s":[{"i":[[0,0],[-3.186,-10.891],[30.926,-7.123]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[36.407,-30.444],[1.634,28.2]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[-3.186,-10.891],[34.926,-8.623]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[5.907,-20.444],[-10.866,28.2]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":138,"s":[{"i":[[0,0],[-3.186,-10.891],[34.926,-8.623]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[5.907,-20.444],[-10.866,28.2]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":148,"s":[{"i":[[0,0],[-3.186,-10.891],[30.926,-7.123]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-38.627,-34.871],[48.759,-6.32],[-2.868,38.495]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[0,0],[-3.186,-10.891],[34.926,-8.623]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[5.907,-20.444],[-10.866,28.2]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":166,"s":[{"i":[[0,0],[-3.186,-10.891],[30.926,-7.123]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-38.627,-34.871],[37.474,-10.05],[-2.868,38.495]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":174,"s":[{"i":[[0,0],[-3.186,-10.891],[34.926,-8.623]],"o":[[24.615,33.652],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[5.907,-20.444],[-10.866,28.2]],"c":false}]},{"t":206,"s":[{"i":[[0,0],[-3.186,-10.891],[32.263,-10.878]],"o":[[30.709,-3.412],[3.186,10.891],[0,0]],"v":[[-43.056,-26.074],[42.907,-7.444],[-34.866,26.7]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.980392158031,0.564705908298,0.086274512112,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.835294127464,0.152941182256,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"wing_bl","parent":21,"sr":1,"ks":{"p":{"a":0,"k":[-18.164,4.659,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,0],[-7.962,3.64]],"o":[[10.236,-7.279],[0,0]],"v":[[7.853,-2.894],[36.676,-24.112]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":74,"s":[{"i":[[0,0],[-7.962,3.64]],"o":[[10.236,-7.279],[0,0]],"v":[[-16.037,10.236],[16.037,-10.236]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":92,"s":[{"i":[[0,0],[-7.962,3.64]],"o":[[10.236,-7.279],[0,0]],"v":[[-16.037,10.236],[16.037,-10.236]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[0,0],[-6.982,5.417]],"o":[[8.72,9.244],[0,0]],"v":[[-0.179,-9.033],[29.27,-5.606]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":110,"s":[{"i":[[0,0],[-11.698,3.051]],"o":[[9.552,1.392],[0,0]],"v":[[-13.278,-9.676],[17.485,-11.31]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[-6.982,5.417]],"o":[[8.72,9.244],[0,0]],"v":[[-0.179,-9.033],[29.27,-5.606]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":138,"s":[{"i":[[0,0],[-6.982,5.417]],"o":[[8.72,9.244],[0,0]],"v":[[-0.179,-9.033],[29.27,-5.606]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":148,"s":[{"i":[[0,0],[-11.698,3.051]],"o":[[9.552,1.392],[0,0]],"v":[[-13.278,-9.676],[17.485,-11.31]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[0,0],[-6.982,5.417]],"o":[[8.72,9.244],[0,0]],"v":[[-0.179,-9.033],[29.27,-5.606]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":166,"s":[{"i":[[0,0],[-11.698,3.051]],"o":[[9.552,1.392],[0,0]],"v":[[-13.278,-9.676],[17.485,-11.31]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":174,"s":[{"i":[[0,0],[-6.982,5.417]],"o":[[8.72,9.244],[0,0]],"v":[[-0.179,-9.033],[29.27,-5.606]],"c":false}]},{"t":206,"s":[{"i":[[0,0],[-7.962,3.64]],"o":[[10.236,-7.279],[0,0]],"v":[[-16.037,10.236],[16.037,-10.236]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":216,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[5]},{"t":240,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":216,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[100]},{"t":240,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"wing","parent":28,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[-36.067]},{"t":74,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[-122.157,-8.399,0],"to":[0,0,0],"ti":[0,0,0]},{"t":74,"s":[-112.784,-35.476,0]}]},"a":{"a":0,"k":[30.5,-12,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,0],[-2.958,-13.61],[-32.973,12.882]],"o":[[-45.283,2.228],[2.958,13.61],[0,0]],"v":[[49.746,-37.599],[-29.776,18.921],[21.241,13.628]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":74,"s":[{"i":[[0,0],[-2.958,-13.61],[-32.973,12.882]],"o":[[-38.103,24.016],[2.958,13.61],[0,0]],"v":[[13.129,-32.598],[-54.179,26.243],[54.368,16.381]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":92,"s":[{"i":[[0,0],[-2.958,-13.61],[-32.973,12.882]],"o":[[-38.103,24.016],[2.958,13.61],[0,0]],"v":[[13.129,-32.598],[-54.179,26.243],[54.368,16.381]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-1.344,54.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-26.679,-12.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":110,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-6.844,30.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-43.179,-9.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-1.344,54.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-26.679,-12.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":138,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-1.344,54.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-26.679,-12.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":148,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-6.844,30.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-43.179,-9.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-1.344,54.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-26.679,-12.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":166,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-6.844,30.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-43.179,-9.757],[54.368,16.381]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":174,"s":[{"i":[[0,0],[3.066,-13.586],[-57.583,27.6]],"o":[[-1.344,54.579],[-6.035,26.739],[0,0]],"v":[[13.129,-32.598],[-26.679,-12.757],[54.368,16.381]],"c":false}]},{"t":206,"s":[{"i":[[0,0],[-2.958,-13.61],[-32.973,12.882]],"o":[[-38.103,24.016],[2.958,13.61],[0,0]],"v":[[13.129,-32.598],[-54.179,26.243],[54.368,16.381]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.980392158031,0.564705908298,0.086274512112,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.835294127464,0.152941182256,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"O_bl4","parent":25,"sr":1,"ks":{"p":{"a":0,"k":[-30.789,17.913,0]},"a":{"a":0,"k":[225.211,273.913,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.677,2.747]],"o":[[2.871,-2.545],[0,0]],"v":[[-4.163,3.971],[4.163,-3.971]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[355.926,378.368]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.539,15.159]],"o":[[8.642,-12.85],[0,0]],"v":[[-9.983,21.103],[9.983,-21.103]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[387.942,331.3]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-9.945,17.23]],"o":[[4.477,-19.951],[0,0]],"v":[[-10.94,28.009],[10.94,-28.009]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[63.436,193.496]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":82.133,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":92.268,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":102.4,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":112.533,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":122.666,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132.801,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":142.934,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":153.066,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":163.199,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":173.334,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":183.467,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":193.6,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":203.732,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":213.867,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":224,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":234.133,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":244.266,"s":[0]},{"t":254.400390625,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":82.133,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":92.268,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":102.4,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":112.533,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":122.666,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132.801,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":142.934,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":153.066,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":163.199,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":173.334,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":183.467,"s":[95]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":193.6,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":203.732,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":213.867,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":224,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":234.133,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":244.266,"s":[95]},{"t":254.400390625,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":72,"op":300,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"O_bl3","parent":25,"sr":1,"ks":{"p":{"a":0,"k":[-16.641,-12.754,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,86.122]],"o":[[86.123,0],[0,0]],"v":[[8.904,178.402],[165.093,22.215]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,29.563],[-110.621,0],[-31.544,-69.527]],"o":[[-11.531,-25.293],[0,-110.621],[81.186,0],[0,0]],"v":[[-173.753,105.162],[-191.713,22.215],[8.904,-178.402],[191.713,-60.4]],"c":false}},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":72,"op":300,"st":0,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":"O_sh 2","parent":25,"sr":1,"ks":{"o":{"a":0,"k":33},"p":{"a":0,"k":[14.477,31.675,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-86.123]],"o":[[-86.123,0],[0,0]],"v":[[-22.214,-178.402],[-178.402,-22.214]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-31.026],[110.62,0],[34.692,28.642]],"o":[[12.629,26.251],[0,110.62],[-48.397,0],[0,0]],"v":[[158.694,-108.895],[178.402,-22.214],[-22.214,178.402],[-149.752,132.534]],"c":false}},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":72,"op":300,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"O 2","parent":32,"sr":1,"ks":{},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[82.394,0],[0,-82.394],[-82.394,0],[0,82.394]],"o":[[-82.394,0],[0,82.394],[82.394,0],[0,-82.394]],"v":[[-7.737,-139.727],[-156.925,9.461],[-7.737,158.649],[141.451,9.461]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-114.664],[114.664,0],[0,114.664],[-114.664,0]],"o":[[0,114.664],[-114.664,0],[0,-114.664],[114.664,0]],"v":[[199.88,9.461],[-7.737,217.078],[-215.354,9.461],[-7.737,-198.156]],"c":true}},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":7},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.800000011921,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":72,"op":300,"st":0,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":"body_bl","parent":28,"sr":1,"ks":{"p":{"a":0,"k":[13.403,71.866,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,0],[-12.996,10.203]],"o":[[22.355,-3.168],[0,0]],"v":[[0.585,-6.873],[82.893,-37.54]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":74,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-42.899,9.408],[42.899,-9.408]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":94,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-42.899,9.408],[42.899,-9.408]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":108,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-47.294,25.746],[38.503,6.93]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-42.899,9.408],[42.899,-9.408]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":142,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-42.899,9.408],[42.899,-9.408]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":158,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-23.208,22.695],[62.589,3.879]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":166,"s":[{"i":[[0,0],[-16.794,12.189]],"o":[[26.059,-8.878],[0,0]],"v":[[-19.971,26.91],[61.956,-9.689]],"c":false}]},{"t":204,"s":[{"i":[[0,0],[-14.3,8.279]],"o":[[22.578,0],[0,0]],"v":[[-42.899,9.408],[42.899,-9.408]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.988235294819,0.933333337307,0.129411771894,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":72,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":84,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":120,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":216,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[0]},{"t":240,"s":[5]}]},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":72,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":96,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":108,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":120,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":132,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":144,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":156,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":192,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":204,"s":[95]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":216,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[95]},{"t":240,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":27,"ty":3,"nm":"NULL CONTROL","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[255.148,296.54,0]},"a":{"a":0,"k":[60,60,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[75,75,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":72,"s":[105,105,100]},{"t":88,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"body","parent":27,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[10.154]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":92,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":106,"s":[4.619]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":138,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":152,"s":[-3.668]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":178,"s":[-3.668]},{"t":210,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[59.148,100.54,0],"to":[0,0,0],"ti":[0,0,0]},{"t":74,"s":[60.148,51.54,0]}]},"a":{"a":0,"k":[-4.687,2.752,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":68,"s":[110,90,100]},{"t":76,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[{"i":[[0,0],[-15.556,-53.369],[15.033,63.171],[28.502,8.651]],"o":[[-27.016,8.344],[17.508,60.067],[-14.305,-60.113],[0,0]],"v":[[-105.574,-57.937],[-130.991,60.125],[127.469,10.691],[59.762,-82.547]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":74,"s":[{"i":[[0,0],[-34.956,-43.224],[33.715,79.992],[29.009,6.756]],"o":[[-26.408,10.105],[40.579,50.177],[-18.496,-43.883],[0,0]],"v":[[-99.606,-49.459],[-120.972,79.715],[133.703,-3.121],[22.062,-99.289]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":94,"s":[{"i":[[0,0],[-34.956,-43.224],[33.715,79.992],[29.009,6.756]],"o":[[-26.408,10.105],[40.579,50.177],[-18.496,-43.883],[0,0]],"v":[[-99.606,-49.459],[-120.972,79.715],[133.703,-3.121],[22.062,-99.289]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":108,"s":[{"i":[[0,0],[-19.531,-52.045],[-6.786,86.541],[29.009,6.756]],"o":[[-26.408,10.105],[23.865,63.592],[7.085,-90.358],[0,0]],"v":[[-99.606,-49.459],[-127.819,82.188],[130.216,19.168],[22.062,-99.289]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[-34.956,-43.224],[33.715,79.992],[29.009,6.756]],"o":[[-26.408,10.105],[40.579,50.177],[-18.496,-43.883],[0,0]],"v":[[-99.606,-49.459],[-120.972,79.715],[133.703,-3.121],[22.062,-99.289]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":142,"s":[{"i":[[0,0],[-34.956,-43.224],[33.715,79.992],[29.009,6.756]],"o":[[-26.408,10.105],[40.579,50.177],[-18.496,-43.883],[0,0]],"v":[[-99.606,-49.459],[-120.972,79.715],[133.703,-3.121],[22.062,-99.289]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":158,"s":[{"i":[[0,0],[-19.531,-52.045],[-6.786,86.541],[29.009,6.756]],"o":[[-26.408,10.105],[23.865,63.592],[7.085,-90.358],[0,0]],"v":[[-99.606,-49.459],[-137.819,68.688],[137.216,33.169],[22.062,-99.289]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":166,"s":[{"i":[[0,0],[-35.72,-42.595],[33.715,79.992],[29.009,6.756]],"o":[[-26.408,10.105],[56.989,67.958],[-18.496,-43.883],[0,0]],"v":[[-99.606,-49.459],[-103.972,94.715],[134.703,-15.621],[22.062,-99.289]],"c":false}]},{"t":204,"s":[{"i":[[0,0],[-34.956,-43.224],[33.715,79.992],[29.009,6.756]],"o":[[-26.408,10.105],[40.579,50.177],[-18.496,-43.883],[0,0]],"v":[[-99.606,-49.459],[-120.972,79.715],[133.703,-3.121],[22.062,-99.289]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.980392158031,0.564705908298,0.086274512112,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.835294127464,0.152941182256,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":"O_bl2","parent":32,"sr":1,"ks":{"p":{"a":0,"k":[-30.789,17.913,0]},"a":{"a":0,"k":[225.211,273.913,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.677,2.747]],"o":[[2.871,-2.545],[0,0]],"v":[[-4.163,3.971],[4.163,-3.971]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[355.926,378.368]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.539,15.159]],"o":[[8.642,-12.85],[0,0]],"v":[[-9.983,21.103],[9.983,-21.103]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[387.942,331.3]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-9.945,17.23]],"o":[[4.477,-19.951],[0,0]],"v":[[-10.94,28.009],[10.94,-28.009]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[63.436,193.496]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":72,"st":0,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"O_bl1","parent":32,"sr":1,"ks":{"p":{"a":0,"k":[-16.641,-12.754,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,86.122]],"o":[[86.123,0],[0,0]],"v":[[8.904,178.402],[165.093,22.215]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,29.563],[-110.621,0],[-31.544,-69.527]],"o":[[-11.531,-25.293],[0,-110.621],[81.186,0],[0,0]],"v":[[-173.753,105.162],[-191.713,22.215],[8.904,-178.402],[191.713,-60.4]],"c":false}},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":72,"st":0,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":"O_sh","parent":32,"sr":1,"ks":{"o":{"a":0,"k":33},"p":{"a":0,"k":[14.477,31.675,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-86.123]],"o":[[-86.123,0],[0,0]],"v":[[-22.214,-178.402],[-178.402,-22.214]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-31.026],[110.62,0],[34.692,28.642]],"o":[[12.629,26.251],[0,110.62],[-48.397,0],[0,0]],"v":[[158.694,-108.895],[178.402,-22.214],[-22.214,178.402],[-149.752,132.534]],"c":false}},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":72,"st":0,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"O","parent":27,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.04],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[60]},{"t":72,"s":[0]}]},"p":{"a":0,"k":[60.852,19.46,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":60,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":72,"s":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":86,"s":[94,94,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":94,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":100,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":110,"s":[108,95,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":118,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":138,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":148,"s":[108,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":156,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":166,"s":[108,95,100]},{"t":174,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[82.394,0],[0,-82.394],[-82.394,0],[0,82.394]],"o":[[-82.394,0],[0,82.394],[82.394,0],[0,-82.394]],"v":[[-7.737,-139.727],[-156.925,9.461],[-7.737,158.649],[141.451,9.461]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-114.664],[114.664,0],[0,114.664],[-114.664,0]],"o":[[0,114.664],[-114.664,0],[0,-114.664],[114.664,0]],"v":[[199.88,9.461],[-7.737,217.078],[-215.354,9.461],[-7.737,-198.156]],"c":true}},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.639215707779,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":7},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.800000011921,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":72,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"_051_GHSTBST","refId":"comp_0","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":180,"st":-60,"bm":0}]} \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/hourglass.png b/Telegram/Resources/icons/menu/hourglass.png new file mode 100644 index 0000000000000000000000000000000000000000..e7389fb11a82c6da93e062a13e1b78b8a67b237c GIT binary patch literal 630 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf?186?V~B;| z+mN%X9UWz!eyv$DC8<$JD5xW1^TZ_ADO;D^xV0&DrOpzawK@@;^$d}x+p6p~ZwTGz zy7%&K)BJU(KlAU|y<@KB^E2aUg)4i2yg;+M+%;`Fsv@qbuugZ+hgvA$EglHu;&zx}HarfPGPm9i*%IVKOTedrP z-LHd7-4;*0{`%=B9_CC7nSDW#dyXdUy#GFL`|P9tcw+Rfzb)GwwKnFZWiyAslEV)d zr0%{8w8?v^kng^OOpOa-^y1fFPcb?f5xnxs(;`Mq5w80_&rU52;Mgj{)f%F8v@dC+ z#C7Kx>WRiu``z5VvvhXn_suD#xR{PEAYYVt{jBaaIwY>iqQwf0gS+vcj? zg=f-?cdmMmcJ2n9@W6c5#J+SF^qr^cLj)Ka;%QRr>mdKI;Vst E0K!)eg#Z8m literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/hourglass@2x.png b/Telegram/Resources/icons/menu/hourglass@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..69a579183a251511c7f25c6a39cc9bf905bf153a GIT binary patch literal 1200 zcmV;h1W)^kP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG8c9S!R9Fe^SX(HpQ50s3OA0YA z$2Eo;4|t=L+!Epe2@jg&ZBp_;h~)Ah;lbO?<3v%EJdjIDA$dSvJP_qJ({YW+CAae( z>-3*(#{BK^&p7Ad-w)IJ*ZS7C_Fn(mYwv$tT;w5VK+b@ifq$C;SCQ~4m8zws<;VL7 z3kwrL@|etK^UwFu-Q9h0alzwaiK(rveRIM9tz{(US;@$j;>5&6T3Q+sq|siT;x@9gaG;JGkh+~40v9gK{Oa5*rb!NEZ|9v&Xjv>1ytv9q%iy!ZF_ zlz&b#fNySY;MmjC!|B2cQ>)c4FE5~;ot@=h>(tZ~T%MnwQO8EBnb{@Rl7&N!JxjBk4K0Y287-);a&(Cjkbd-?G%gb(VZnlt8^{9!pwKa-_ z&RbboY0X$(UT!j(2(!AnD*X_&4)H4_thuzblu8ZfOmA;5z^v7Y za!N|d*47s0gnfsGNs$o#`uZA5oLLze8BPrUDRpvkG9@WJJ)HnaNlB#1%gg)Jg-6MC zbXXIiPPJMs>2c8^;q2^;go1(s{?=ekZnp+?y0o-J$(Wy?XZgozH&d<-Yv$Ms+#KgqltpSV$p!~(bU$J7{3kwSf@bK`En3%{?pj#S^ z#({wWZ*Ol_j|Jc|4;S1NaEoP44v>qBi$RT1UtiDC#l^+pwnQd0@zBsv)>>FtNT!O4 z3Rdp`8HX3B(eaQffe!8Q^z@WM((l2+K|(@;0D!Q$xd{^vK~^bM##W+Q#(^(zVfDAS zw+My;udc3cYHGr9fk!m!+}vCNR##UCC3ak7WTc>!DqwSg6eBh^mbh|qa_Bh2ZpzQk zCuLb#8BP+wFhu}JiKg}L`c_m_#50e-`4>Uc;_mM5_V)G&hFykcjRPOI?3tMvQjU#{ z;RcO7YSjXsjsgOt3&|_Zoa<0RaI3MB}`VN1STIJ9|4T(J={Se zXru~5LqlL(TwMGb5uGz2;c*xHvjK0azrP=!(kLB#>`DBT#;px+9G}t+4Gr|=$uqwX znE&q2hT-Ai=;&x<2-_aQhsoOifPQ^>d5H{x)_!l1~2)EP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>I!Q!9RA>e5T1iMAOAwA*T+z4? zqiEESxFtpc3JNNUiXa|D#3&jMUKBiNqL6^MD8V2qMg&hTkc)~2f`SO~AS$?=)Ffhz z;u`mTiK6Cx(2t@s|28xKqVF;PsixfkNOM2!NEkbxVXqHQj17I!W`on_xJbd@y?w)BpXCZM`+x+S-_TR0H?!-P4=zUxLukP+CzsIyz+2${dQY_xbZ@X4TBh zEH^h-wg&>m?Cfl6>FMbq*>nPu;nC3%i!P?Pm6etLwn4JAw8S{Ew79sq=+7yK;lqay zG;H3yc_WA7Z=K4@N+N7*Z2bGB37`D@{LatMnJXxH0s{l}F&-NmdwF@u*gzW)5TFm6 zzPjb*Q!{rh+7+t;sOA3S)VLr3Mcx3@Q? zcu@WL@dHJv3d-aS{{H@3TU*pO$Vie%l_DL<$;sK+*dX=2y*)jLv`D+6V3CoLhlhvc z9E8!)(TIo$?aJ6+4GawMTBr=;;^MSxnffgXE0p29msq$G|{Le3u|MH8nw96?ubN!otFahlj~;OgRS!2Z4X{^Ya84 z8yky`jurrJ=p!;mYildHo|2NnJBr*uue!Rr#HSgaM5ipjR@6cbzcI83ASjW2eSPUw zOG^vD^k_iAC>?`;m__6zvI5Bvklg7pJw2V|n=ZJyxpjAU(_lL}Il<{F@AU24w^1|` z;N!=SSfF{pO_Y<8l5lE3PD7x>EZ*Isq9PuHI`iGTcPL_b%uSIkEiLQo>v?xk(Y}5A zR#R@MuCC_6kg=wFd3oKG{xCmmY;5@92QM*^l9Q9Qdx>HS$9R1A*4EZgplR1K-CGL_ zi{9Q|-oO6-eh&{1F&-cyuV25$P$YFMaUyla@au)Cs;VNlAYk{2VJV3bMI)b1hK7c4 zvm=SY@Zf}m1nLzgRbF16CL7Mq&RWs~&Jy_=Uu_|#iP`Y&NZixY)6_9dyF-K@NASOY z|Hg4zety0tKd?o(xVT_!IPj3YXVt_np}nA>fO<7CF@b)nDq|7*^yw49AYkZf8fpBf zuBfO`Wiv$!@;L3CaYXU-=~Gp!DA+M!_+Bx&v$IpMYBODIoN;l(F!+1z?CexIjoyMC zA$g4dMAaT@LVlnUmXJ%%Y2fMqwf^?Udn21e2`G;c++VE#)W+IEw zE9l0@#|zxWbY%cR^(=rHm!0@4Pq{#uc?gz^yU9 zZNYagY%$-*95b9vTtvMzhA$dd8C`ORkF-J#7VpqK;hNEI`BJsDVh2e;j z$azE5C`ORhG5Q>Ww}(8=UrCFxsvYC$=}7<>VrS-3KZhU&-NJwC0~X!=Aq1Q2G_6f9 zzyWebrywES<787Oolyaj91wda0+0F6`cMHI{3{5xL1EWRcxA-T`qUa$8kGnr5l|wa mL_mpv5&qTQ;Pl*BiOwF0000../../animations/noresults.tgs ../../animations/hello_status.tgs ../../animations/starref_link.tgs + ../../animations/media_forbidden.tgs ../../animations/dice/dice_idle.tgs ../../animations/dice/dart_idle.tgs diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 8d265ab8a7..9c9f17b8d4 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1566,3 +1566,27 @@ processingVideoView: RoundButton(defaultActiveButton) { textBgOver: transparent; ripple: emptyRippleAnimation; } + +frozenBarTitle: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; + textFg: attentionButtonFg; +} +frozenRestrictionTitle: FlatLabel(frozenBarTitle) { + align: align(top); +} +frozenBarSubtitle: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} +frozenRestrictionSubtitle: FlatLabel(frozenBarSubtitle) { + align: align(top); +} +frozenInfoBox: Box(defaultBox) { + buttonPadding: margins(16px, 11px, 16px, 16px); + buttonHeight: 42px; + button: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + style: semiboldTextStyle; + } + shadowIgnoreTopSkip: true; +} diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 1e783b4529..4288d0c794 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -11,38 +11,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" // History::session #include "history/history_item.h" // HistoryItem::originalText #include "history/history_item_helpers.h" // DropDisallowedCustomEmoji +#include "base/unixtime.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "base/event_filter.h" #include "ui/chat/chat_style.h" #include "ui/layers/generic_box.h" +#include "ui/basic_click_handlers.h" #include "ui/rect.h" #include "core/shortcuts.h" #include "core/application.h" #include "core/core_settings.h" #include "core/ui_integration.h" +#include "lottie/lottie_icon.h" +#include "info/profile/info_profile_icon.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/shadow.h" #include "ui/power_saving.h" +#include "ui/vertical_list.h" #include "ui/ui_utility.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_document.h" #include "data/stickers/data_custom_emoji.h" #include "chat_helpers/emoji_suggestions_widget.h" +#include "history/view/controls/compose_controls_common.h" #include "window/window_session_controller.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "main/main_session.h" +#include "settings/settings_common.h" #include "settings/settings_premium.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_credits.h" +#include "styles/style_dialogs.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "base/qt/qt_common_adapters.h" @@ -1187,10 +1197,10 @@ base::unique_qptr CreateDisabledFieldView( return result; } -base::unique_qptr TextErrorSendRestriction( +std::unique_ptr TextErrorSendRestriction( QWidget *parent, const QString &text) { - auto result = base::make_unique_q(parent); + auto result = std::make_unique(parent); const auto raw = result.get(); const auto label = CreateChild( result.get(), @@ -1215,11 +1225,11 @@ base::unique_qptr TextErrorSendRestriction( return result; } -base::unique_qptr PremiumRequiredSendRestriction( +std::unique_ptr PremiumRequiredSendRestriction( QWidget *parent, not_null user, not_null controller) { - auto result = base::make_unique_q(parent); + auto result = std::make_unique(parent); const auto raw = result.get(); const auto label = CreateChild( result.get(), @@ -1254,6 +1264,196 @@ base::unique_qptr PremiumRequiredSendRestriction( return result; } +std::unique_ptr BoostsToLiftWriteRestriction( + not_null parent, + std::shared_ptr show, + not_null peer, + int boosts) { + auto result = std::make_unique( + parent, + tr::lng_restricted_boost_group(tr::now), + st::historyComposeButton); + result->setClickedCallback([=] { + const auto window = show->resolveWindow(); + window->resolveBoostState(peer->asChannel(), boosts); + }); + return result; +} + +std::unique_ptr FrozenWriteRestriction( + not_null parent, + std::shared_ptr show, + FrozenWriteRestrictionType type, + FreezeInfoStyleOverride st) { + using namespace Ui; + + auto result = std::make_unique( + parent, + QString(), + st::historyComposeButton); + const auto raw = result.get(); + + const auto bar = (type == FrozenWriteRestrictionType::DialogsList); + const auto title = CreateChild( + raw, + (bar ? tr::lng_frozen_bar_title : tr::lng_frozen_restrict_title)( + tr::now), + bar ? st::frozenBarTitle : st::frozenRestrictionTitle); + title->setAttribute(Qt::WA_TransparentForMouseEvents); + title->show(); + const auto subtitle = CreateChild( + raw, + (bar + ? tr::lng_frozen_bar_text( + lt_arrow, + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), + Ui::Text::WithEntities) + : tr::lng_frozen_restrict_text(Ui::Text::WithEntities)), + bar ? st::frozenBarSubtitle : st::frozenRestrictionSubtitle); + subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); + subtitle->show(); + + const auto shadow = bar ? CreateChild(raw) : nullptr; + const auto icon = bar ? CreateChild(raw) : nullptr; + if (icon) { + icon->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(icon); + st::menuIconDisableAttention.paintInCenter(p, icon->rect()); + }, icon->lifetime()); + icon->show(); + } + + raw->sizeValue() | rpl::start_with_next([=](QSize size) { + if (bar) { + const auto toggle = [&](auto &&widget, bool shown) { + if (widget->isHidden() == shown) { + widget->setVisible(shown); + } + }; + const auto small = 2 * st::defaultDialogRow.photoSize; + const auto shown = (size.width() > small); + toggle(icon, !shown); + toggle(title, shown); + toggle(subtitle, shown); + icon->setGeometry(0, 0, size.width(), size.height()); + } + const auto skip = bar + ? st::defaultDialogRow.padding.left() + : 2 * st::normalFont->spacew; + const auto available = size.width() - skip * 2; + title->resizeToWidth(available); + subtitle->resizeToWidth(available); + const auto height = title->height() + subtitle->height(); + const auto top = (size.height() - height) / 2; + title->moveToLeft(skip, top, size.width()); + subtitle->moveToLeft(skip, top + title->height(), size.width()); + + const auto line = st::lineWidth; + if (shadow) { + shadow->setGeometry(0, size.height() - line, size.width(), line); + } + }, title->lifetime()); + + const auto info = show->session().frozen(); + const auto detailsBox = [=](not_null box) { + box->setWidth(st::boxWideWidth); + box->setStyle(st::frozenInfoBox); + box->setNoContentMargin(true); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + const auto content = box->verticalLayout(); + auto icon = Settings::CreateLottieIcon( + content, + { + .name = u"media_forbidden"_q, + .sizeOverride = { + st::changePhoneIconSize, + st::changePhoneIconSize, + }, + }, + st::settingLocalPasscodeIconPadding); + content->add(std::move(icon.widget)); + box->setShowFinishedCallback([animate = std::move(icon.animate)] { + animate(anim::repeat::once); + }); + + Ui::AddSkip(content); + + const auto infoRow = [&]( + rpl::producer title, + rpl::producer text, + not_null icon) { + auto raw = content->add( + object_ptr(content)); + raw->add( + object_ptr( + raw, + std::move(title) | Ui::Text::ToBold(), + st.infoTitle ? *st.infoTitle : st::defaultFlatLabel), + st::settingsPremiumRowTitlePadding); + raw->add( + object_ptr( + raw, + std::move(text), + st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext), + st::settingsPremiumRowAboutPadding); + object_ptr( + raw, + *icon, + st::starrefInfoIconPosition); + }; + + content->add( + object_ptr( + content, + tr::lng_frozen_title(), + st.title ? *st.title : st::uniqueGiftTitle), + st::settingsPremiumRowTitlePadding); + + Ui::AddSkip(content, st::defaultVerticalListSkip * 3); + + infoRow( + tr::lng_frozen_subtitle1(), + tr::lng_frozen_text1(Text::WithEntities), + st.violationIcon ? st.violationIcon : &st::menuIconBlock); + infoRow( + tr::lng_frozen_subtitle2(), + tr::lng_frozen_text2(Text::WithEntities), + st.readOnlyIcon ? st.readOnlyIcon : &st::menuIconLock); + infoRow( + tr::lng_frozen_subtitle3(), + tr::lng_frozen_text3( + lt_link, + rpl::single(Text::Link(u"@SpamBot"_q, info.appealUrl)), + lt_date, + rpl::single(TextWithEntities{ + langDayOfMonthFull( + base::unixtime::parse(info.until).date()), + }), + Text::WithEntities), + st.appealIcon ? st.appealIcon : &st::menuIconHourglass); + + const auto button = box->addButton( + tr::lng_frozen_appeal_button(), + [url = info.appealUrl] { UrlClickHandler::Open(url); }); + const auto buttonPadding = st::frozenInfoBox.buttonPadding; + const auto buttonWidth = st::boxWideWidth + - buttonPadding.left() + - buttonPadding.right(); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); + }; + raw->setClickedCallback([=] { + show->show(Box(detailsBox)); + }); + return result; +} + void SelectTextInFieldWithMargins( not_null field, const TextSelection &selection) { diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 7d97d93321..a00b4564bc 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -37,6 +37,10 @@ enum class PauseReason; class Show; } // namespace ChatHelpers +namespace HistoryView::Controls { +struct WriteRestriction; +} // namespace HistoryView::Controls + namespace Ui { class PopupMenu; class Show; @@ -162,13 +166,41 @@ private: [[nodiscard]] base::unique_qptr CreateDisabledFieldView( QWidget *parent, not_null peer); -[[nodiscard]] base::unique_qptr TextErrorSendRestriction( +[[nodiscard]] std::unique_ptr TextErrorSendRestriction( QWidget *parent, const QString &text); -[[nodiscard]] base::unique_qptr PremiumRequiredSendRestriction( +[[nodiscard]] std::unique_ptr PremiumRequiredSendRestriction( QWidget *parent, not_null user, not_null controller); +[[nodiscard]] auto BoostsToLiftWriteRestriction( + not_null parent, + std::shared_ptr show, + not_null peer, + int boosts) +-> std::unique_ptr; + +struct FreezeInfoStyleOverride { + const style::Box *box = nullptr; + const style::FlatLabel *title = nullptr; + const style::FlatLabel *subtitle = nullptr; + const style::icon *violationIcon = nullptr; + const style::icon *readOnlyIcon = nullptr; + const style::icon *appealIcon = nullptr; + const style::FlatLabel *infoTitle = nullptr; + const style::FlatLabel *infoAbout = nullptr; +}; +[[nodiscard]] FreezeInfoStyleOverride DarkFreezeInfoStyle(); + +enum class FrozenWriteRestrictionType { + MessageField, + DialogsList, +}; +[[nodiscard]] std::unique_ptr FrozenWriteRestriction( + not_null parent, + std::shared_ptr show, + FrozenWriteRestrictionType type, + FreezeInfoStyleOverride st = {}); void SelectTextInFieldWithMargins( not_null field, diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 29b9cb451a..81b3330972 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -118,7 +118,10 @@ bool CanSendAnyOf( not_null peer, ChatRestrictions rights, bool forbidInForums) { - if (const auto user = peer->asUser()) { + if (peer->session().frozen() + && !peer->isFreezeAppealChat()) { + return false; + } else if (const auto user = peer->asUser()) { if (user->isInaccessible() || user->isRepliesChat() || user->isVerifyCodes()) { @@ -178,7 +181,13 @@ SendError RestrictionError( not_null peer, ChatRestriction restriction) { using Flag = ChatRestriction; - if (const auto restricted = peer->amRestricted(restriction)) { + if (peer->session().frozen() + && !peer->isFreezeAppealChat()) { + return SendError({ + .text = tr::lng_frozen_restrict_title(tr::now), + .frozen = true, + }); + } else if (const auto restricted = peer->amRestricted(restriction)) { if (const auto user = peer->asUser()) { if (user->requiresPremiumToWrite() && !user->session().premium()) { diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h index bfa24fb869..b3db584a4e 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.h +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -191,16 +191,19 @@ struct SendError { QString text; int boostsToLift = 0; bool premiumToLift = false; + bool frozen = false; }; SendError(Args &&args) : text(std::move(args.text)) , boostsToLift(args.boostsToLift) - , premiumToLift(args.premiumToLift) { + , premiumToLift(args.premiumToLift) + , frozen(args.frozen) { } QString text; int boostsToLift = 0; bool premiumToLift = false; + bool frozen = false; [[nodiscard]] SendError value_or(SendError other) const { return *this ? *this : other; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 20fdd10737..41d5d19e68 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1349,6 +1349,10 @@ bool PeerData::isVerifyCodes() const { return (id == kVerifyCodesId); } +bool PeerData::isFreezeAppealChat() const { + return username().compare(u"spambot"_q, Qt::CaseInsensitive) == 0; +} + bool PeerData::sharedMediaInfo() const { return isSelf() || isRepliesChat(); } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 21aad4cde0..c3f48eb1b6 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -235,6 +235,7 @@ public: [[nodiscard]] bool isGigagroup() const; [[nodiscard]] bool isRepliesChat() const; [[nodiscard]] bool isVerifyCodes() const; + [[nodiscard]] bool isFreezeAppealChat() const; [[nodiscard]] bool sharedMediaInfo() const; [[nodiscard]] bool savedSublistsInfo() const; [[nodiscard]] bool hasStoriesHidden() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a75b229fe1..5886a8318c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "api/api_chat_filters.h" #include "apiwrap.h" +#include "chat_helpers/message_field.h" #include "core/application.h" #include "core/ui_integration.h" #include "core/update_checker.h" @@ -680,6 +681,8 @@ Widget::Widget( || !controller->enoughSpaceForFilters())) { toggleFiltersMenu(true); } + + setupFrozenAccountBar(); } void Widget::setupSwipeBack() { @@ -990,6 +993,29 @@ void Widget::setupTouchChatPreview() { }, _inner->lifetime()); } +void Widget::setupFrozenAccountBar() { + session().frozenValue( + ) | rpl::start_with_next([=] { + updateFrozenAccountBar(); + updateControlsGeometry(); + }, lifetime()); +} + +void Widget::updateFrozenAccountBar() { + if (_layout == Layout::Child + || _openedForum + || _openedFolder + || !session().frozen()) { + _frozenAccountBar = nullptr; + } else if (!_frozenAccountBar) { + _frozenAccountBar = FrozenWriteRestriction( + this, + controller()->uiShow(), + FrozenWriteRestrictionType::DialogsList); + _frozenAccountBar->show(); + } +} + void Widget::setupMoreChatsBar() { if (_layout == Layout::Child) { return; @@ -1418,6 +1444,9 @@ void Widget::updateControlsVisibility(bool fast) { if (_moreChatsBar) { _moreChatsBar->show(); } + if (_frozenAccountBar) { + _frozenAccountBar->show(); + } if (_chatFilters) { _chatFilters->show(); } @@ -1736,6 +1765,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) { if (_stories) { storiesExplicitCollapse(); } + updateFrozenAccountBar(); }, (folder != nullptr), animated); } @@ -1792,6 +1822,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) { _api.request(base::take(_topicSearchRequest)).cancel(); _inner->changeOpenedForum(forum); storiesToggleExplicitExpand(false); + updateFrozenAccountBar(); updateStoriesVisibility(); }, (forum != nullptr), animated); } @@ -2123,6 +2154,9 @@ void Widget::startWidthAnimation() { } _widthAnimationCache = grabNonNarrowScrollFrame(); _scroll->hide(); + if (_frozenAccountBar) { + _frozenAccountBar->hide(); + } if (_chatFilters) { _chatFilters->hide(); } @@ -2133,6 +2167,9 @@ void Widget::stopWidthAnimation() { _widthAnimationCache = QPixmap(); if (!_showAnimation) { _scroll->setVisible(!_suggestions); + if (_frozenAccountBar) { + _frozenAccountBar->setVisible(!_suggestions); + } if (_chatFilters) { _chatFilters->setVisible(!_suggestions); } @@ -2230,6 +2267,9 @@ void Widget::startSlideAnimation( if (_moreChatsBar) { _moreChatsBar->hide(); } + if (_frozenAccountBar) { + _frozenAccountBar->hide(); + } if (_chatFilters) { _chatFilters->hide(); } @@ -3813,9 +3853,17 @@ void Widget::updateControlsGeometry() { if (_chatFilters) { _chatFilters->resizeToWidth(barw); } + if (_frozenAccountBar) { + _frozenAccountBar->resize(barw, _frozenAccountBar->height()); + } _updateScrollGeometryCached = [=] { - const auto moreChatsBarTop = expandedStoriesTop + const auto frozenBarTop = expandedStoriesTop + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded); + if (_frozenAccountBar) { + _frozenAccountBar->move(0, frozenBarTop); + } + const auto moreChatsBarTop = frozenBarTop + + (_frozenAccountBar ? _frozenAccountBar->height() : 0); if (_moreChatsBar) { _moreChatsBar->move(0, moreChatsBarTop); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 698bb56ab1..531b59b38a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -201,6 +201,7 @@ private: void setupSupportMode(); void setupTouchChatPreview(); + void setupFrozenAccountBar(); void setupConnectingWidget(); void setupMainMenuToggle(); void setupMoreChatsBar(); @@ -223,6 +224,7 @@ private: void showMainMenu(); void clearSearchCache(bool clearPosts); void setSearchQuery(const QString &query, int cursorPosition = -1); + void updateFrozenAccountBar(); void updateControlsVisibility(bool fast = false); void updateLockUnlockVisibility( anim::type animated = anim::type::instant); @@ -300,6 +302,9 @@ private: const Layout _layout = Layout::Main; int _narrowWidth = 0; + + std::unique_ptr _frozenAccountBar; + object_ptr _searchControls; object_ptr _subsectionTopBar = { nullptr }; struct { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 48cecb1301..5e2c88b948 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6514,21 +6514,24 @@ void HistoryWidget::updateSendRestriction() { _sendRestrictionKey = restriction.text; if (!restriction) { _sendRestriction = nullptr; + } else if (restriction.frozen) { + const auto show = controller()->uiShow(); + _sendRestriction = FrozenWriteRestriction( + this, + show, + FrozenWriteRestrictionType::MessageField); } else if (restriction.premiumToLift) { _sendRestriction = PremiumRequiredSendRestriction( this, _peer->asUser(), controller()); } else if (const auto lifting = restriction.boostsToLift) { - auto button = base::make_unique_q( + const auto show = controller()->uiShow(); + _sendRestriction = BoostsToLiftWriteRestriction( this, - restriction.text, - st::historyComposeButton); - const auto channel = _peer->asChannel(); - button->setClickedCallback([=] { - controller()->resolveBoostState(channel, lifting); - }); - _sendRestriction = std::move(button); + show, + _peer, + lifting); } else { _sendRestriction = TextErrorSendRestriction(this, restriction.text); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1b0a818bf5..65b5ea7b0a 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -820,7 +820,7 @@ private: bool _cmdStartShown = false; object_ptr _field; base::unique_qptr _fieldDisabled; - base::unique_qptr _sendRestriction; + std::unique_ptr _sendRestriction; using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel; base::unique_qptr _charsLimitation; QString _sendRestrictionKey; diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h index 59a1e555ef..ea4bcb3178 100644 --- a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -40,6 +40,7 @@ enum class WriteRestrictionType { None, Rights, PremiumRequired, + Frozen, }; struct 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 a0b676e44e..404691263a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -2324,15 +2324,17 @@ void SetupRestrictionView( ) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](Controls::WriteRestriction value) { using Type = Controls::WriteRestriction::Type; - if (const auto lifting = value.boostsToLift) { - state->button = std::make_unique( + if (value.type == Type::Frozen) { + state->button = FrozenWriteRestriction( widget, - tr::lng_restricted_boost_group(tr::now), - st::historyComposeButton); - state->button->setClickedCallback([=] { - const auto window = show->resolveWindow(); - window->resolveBoostState(peer->asChannel(), lifting); - }); + show, + FrozenWriteRestrictionType::MessageField); + } else if (const auto lifting = value.boostsToLift) { + state->button = BoostsToLiftWriteRestriction( + widget, + show, + peer, + lifting); } else if (value.type == Type::Rights) { state->icon = nullptr; state->unlock = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 59dd3d6c4b..dd6895dc17 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -671,12 +671,22 @@ void RepliesWidget::setupComposeControls() { : tr::lng_forum_topic_closed(tr::now); }); auto writeRestriction = rpl::combine( + session().frozenValue(), session().changes().peerFlagsValue( _history->peer, Data::PeerUpdate::Flag::Rights), Data::CanSendAnythingValue(_history->peer), std::move(topicWriteRestrictions) - ) | rpl::map([=](auto, auto, Data::SendError topicRestriction) { + ) | rpl::map([=]( + const Main::FreezeInfo &info, + auto, + auto, + Data::SendError topicRestriction) { + if (info) { + return Controls::WriteRestriction{ + .type = Controls::WriteRestrictionType::Frozen, + }; + } const auto allWithoutPolls = Data::AllSendRestrictions() & ~ChatRestriction::SendPolls; const auto canSendAnything = _topic diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 729b6f24a2..812893be3c 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -268,15 +268,22 @@ void ScheduledWidget::setupComposeControls() { : tr::lng_forum_topic_closed(tr::now); }); return rpl::combine( + session().frozenValue(), session().changes().peerFlagsValue( _history->peer, Data::PeerUpdate::Flag::Rights), Data::CanSendAnythingValue(_history->peer), std::move(topicWriteRestrictions) ) | rpl::map([=]( + const Main::FreezeInfo &info, auto, auto, Data::SendError topicRestriction) { + if (info) { + return Controls::WriteRestriction{ + .type = Controls::WriteRestrictionType::Frozen, + }; + } const auto allWithoutPolls = Data::AllSendRestrictions() & ~ChatRestriction::SendPolls; const auto canSendAnything = Data::CanSendAnyOf( @@ -303,11 +310,17 @@ void ScheduledWidget::setupComposeControls() { }() : [&] { return rpl::combine( + session().frozenValue(), session().changes().peerFlagsValue( _history->peer, Data::PeerUpdate::Flag::Rights), Data::CanSendAnythingValue(_history->peer) - ) | rpl::map([=] { + ) | rpl::map([=](const Main::FreezeInfo &info, auto, auto) { + if (info) { + return Controls::WriteRestriction{ + .type = Controls::WriteRestrictionType::Frozen, + }; + } const auto allWithoutPolls = Data::AllSendRestrictions() & ~ChatRestriction::SendPolls; const auto canSendAnything = Data::CanSendAnyOf( diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 4326d7559c..594bad12da 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -24,6 +24,7 @@ AppConfig::AppConfig(not_null account) : _account(account) { ) | rpl::filter([=](Session *session) { return (session != nullptr); }) | rpl::start_with_next([=] { + _lastFrozenRefresh = 0; refresh(); }, _lifetime); } @@ -35,6 +36,18 @@ void AppConfig::start() { ) | rpl::start_with_next([=](not_null instance) { _api.emplace(instance); refresh(); + + _frozenTrackLifetime = instance->frozenErrorReceived( + ) | rpl::start_with_next([=] { + if (!get(u"freeze_since_date"_q, 0)) { + const auto now = crl::now(); + if (!_lastFrozenRefresh + || now > _lastFrozenRefresh + kRefreshTimeout) { + _lastFrozenRefresh = now; + refresh(); + } + } + }); }, _lifetime); } diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 308656bbe4..1cf3aa89cd 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -124,6 +124,9 @@ private: std::vector _startRefPrefixes; + crl::time _lastFrozenRefresh = 0; + rpl::lifetime _frozenTrackLifetime; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 2d846d30a0..77609c60ea 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -160,15 +160,6 @@ Session::Session( } }, _lifetime); -#ifndef OS_MAC_STORE - appConfig().value( - ) | rpl::start_with_next([=] { - _premiumPossible = !appConfig().get( - u"premium_purchase_blocked"_q, - true); - }, _lifetime); -#endif // OS_MAC_STORE - if (_settings->hadLegacyCallsPeerToPeerNobody()) { api().userPrivacy().save( Api::UserPrivacy::Key::CallsPeer2Peer, @@ -204,6 +195,27 @@ Session::Session( _api->requestNotifySettings(MTP_inputNotifyBroadcasts()); Core::App().downloadManager().trackSession(this); + + appConfig().value( + ) | rpl::start_with_next([=] { + appConfigRefreshed(); + }, _lifetime); +} + +void Session::appConfigRefreshed() { + const auto &config = appConfig(); + + _frozen = FreezeInfo{ + .since = config.get(u"freeze_since_date"_q, 0), + .until = config.get(u"freeze_until_date"_q, 0), + .appealUrl = config.get(u"freeze_appeal_url"_q, QString()), + }; + +#ifndef OS_MAC_STORE + _premiumPossible = !config.get( + u"premium_purchase_blocked"_q, + true); +#endif // OS_MAC_STORE } void Session::setTmpPassword(const QByteArray &password, TimeId validUntil) { @@ -431,6 +443,14 @@ Support::FastButtonsBots &Session::fastButtonsBots() const { return *_fastButtonsBots; } +FreezeInfo Session::frozen() const { + return _frozen.current(); +} + +rpl::producer Session::frozenValue() const { + return _frozen.value(); +} + void Session::addWindow(not_null controller) { _windows.emplace(controller); controller->lifetime().add([=] { diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 677405d28c..65543d0969 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -80,6 +80,19 @@ class Domain; class SessionSettings; class SendAsPeers; +struct FreezeInfo { + TimeId since = 0; + TimeId until = 0; + QString appealUrl; + + explicit operator bool() const { + return since != 0; + } + friend inline bool operator==( + const FreezeInfo &, + const FreezeInfo &) = default; +}; + class Session final : public base::has_weak_ptr { public: Session( @@ -236,12 +249,17 @@ public: [[nodiscard]] Support::Templates &supportTemplates() const; [[nodiscard]] Support::FastButtonsBots &fastButtonsBots() const; + [[nodiscard]] FreezeInfo frozen() const; + [[nodiscard]] rpl::producer frozenValue() const; + [[nodiscard]] auto colorIndicesValue() -> rpl::producer; private: static constexpr auto kDefaultSaveDelay = crl::time(1000); + void appConfigRefreshed(); + const UserId _userId; const not_null _account; @@ -288,6 +306,8 @@ private: base::flat_set> _windows; base::Timer _saveSettingsTimer; + rpl::variable _frozen; + QByteArray _tmpPassword; TimeId _tmpPasswordValidUntil = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 21ae900503..9cc85d4ab7 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -841,7 +841,9 @@ void ReplyArea::show( peer ) | rpl::map([=](bool can) { using namespace HistoryView::Controls; - return (can + return user->session().frozen() + ? WriteRestriction{ .type = WriteRestrictionType::Frozen } + : (can || !user || !user->requiresPremiumToWrite() || user->session().premium()) diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index ee10958370..1890c4485d 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -100,6 +100,7 @@ public: [[nodiscard]] auto nonPremiumDelayedRequests() const -> rpl::producer; + [[nodiscard]] rpl::producer<> frozenErrorReceived() const; void restart(); void restart(ShiftedDcId shiftedDcId); @@ -286,6 +287,7 @@ private: Fn _sessionResetHandler; rpl::event_stream _nonPremiumDelayedRequests; + rpl::event_stream<> _frozenErrorReceived; base::Timer _checkDelayedTimer; @@ -562,6 +564,10 @@ auto Instance::Private::nonPremiumDelayedRequests() const return _nonPremiumDelayedRequests.events(); } +rpl::producer<> Instance::Private::frozenErrorReceived() const { + return _frozenErrorReceived.events(); +} + void Instance::Private::requestConfigIfOld() { const auto timeout = _config->values().blockedMode ? kConfigBecomesOldForBlockedIn @@ -1593,6 +1599,8 @@ bool Instance::Private::onErrorDefault( return true; } else if (type == u"CONNECTION_LANG_CODE_INVALID"_q) { Lang::CurrentCloudManager().resetToDefault(); + } else if (type == u"FROZEN_METHOD_INVALID"_q) { + _frozenErrorReceived.fire({}); } if (badGuestDc) _badGuestDcRequests.erase(requestId); return false; @@ -1920,6 +1928,10 @@ rpl::producer Instance::nonPremiumDelayedRequests() const { return _private->nonPremiumDelayedRequests(); } +rpl::producer<> Instance::frozenErrorReceived() const { + return _private->frozenErrorReceived(); +} + void Instance::requestConfigIfOld() { _private->requestConfigIfOld(); } diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.h b/Telegram/SourceFiles/mtproto/mtp_instance.h index 55401065f6..eeb56d29cc 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.h +++ b/Telegram/SourceFiles/mtproto/mtp_instance.h @@ -142,6 +142,7 @@ public: [[nodiscard]] auto nonPremiumDelayedRequests() const -> rpl::producer; + [[nodiscard]] rpl::producer<> frozenErrorReceived() const; void syncHttpUnixtime(); diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index f010afa62f..c7b94f0b06 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -177,6 +177,7 @@ menuIconUnique: icon {{ "menu/unique", menuIconColor }}; menuIconNftWear: icon {{ "menu/nft_wear", menuIconColor }}; menuIconNftTakeOff: icon {{ "menu/nft_takeoff", menuIconColor }}; menuIconShortcut: icon {{ "menu/shortcut", menuIconColor }}; +menuIconHourglass: icon {{ "menu/hourglass", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); From fbab3964e68674f6e7a042f70f529d32f989de1f Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 11 Mar 2025 16:09:34 +0400 Subject: [PATCH 097/122] Handle some frozen cases. --- .../history/history_inner_widget.cpp | 4 +++- .../history/view/history_view_list_widget.cpp | 4 +++- .../view/history_view_top_bar_widget.cpp | 8 ++++++-- .../SourceFiles/main/session/session_show.cpp | 11 +++++++++++ .../SourceFiles/main/session/session_show.h | 3 +++ .../SourceFiles/window/window_peer_menu.cpp | 17 +++++++++++++---- Telegram/SourceFiles/window/window_peer_menu.h | 4 +++- .../window/window_session_controller.cpp | 4 ++++ .../window/window_session_controller.h | 2 ++ 9 files changed, 48 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 8abce4dd04..ebb1c906fc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2177,7 +2177,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } const auto link = ClickHandler::getActive(); - if (link + if (_controller->showFrozenError()) { + return; + } else if (link && !link->property( kSendReactionEmojiProperty).value().empty() && _reactionsManager->showContextMenu( diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 7a2d62cd9b..21df444337 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2811,7 +2811,9 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } const auto link = ClickHandler::getActive(); - if (link + if (controller()->showFrozenError()) { + return; + } else if (link && !link->property( kSendReactionEmojiProperty).value().empty() && _reactionsManager diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 2e9ac641b4..62d5d43a94 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -271,7 +271,9 @@ void TopBarWidget::refreshLang() { } void TopBarWidget::call() { - if (const auto peer = _activeChat.key.peer()) { + if (_controller->showFrozenError()) { + return; + } else if (const auto peer = _activeChat.key.peer()) { if (const auto user = peer->asUser()) { Core::App().calls().startOutgoingCall(user, false); } @@ -279,7 +281,9 @@ void TopBarWidget::call() { } void TopBarWidget::groupCall() { - if (const auto peer = _activeChat.key.peer()) { + if (_controller->showFrozenError()) { + return; + } else if (const auto peer = _activeChat.key.peer()) { if (HasGroupCallMenu(peer)) { showGroupCallMenu(peer); } else { diff --git a/Telegram/SourceFiles/main/session/session_show.cpp b/Telegram/SourceFiles/main/session/session_show.cpp index a9914e87d8..5dcebcdedc 100644 --- a/Telegram/SourceFiles/main/session/session_show.cpp +++ b/Telegram/SourceFiles/main/session/session_show.cpp @@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "main/session/session_show.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" + namespace Main { namespace { @@ -70,6 +73,14 @@ Session &SimpleSessionShow::session() const { } // namespace +bool SessionShow::showFrozenError() { + if (!session().frozen()) { + return false; + } + showToast(tr::lng_frozen_bar_title(tr::now)); + return true; +} + std::shared_ptr MakeSessionShow( std::shared_ptr show, not_null session) { diff --git a/Telegram/SourceFiles/main/session/session_show.h b/Telegram/SourceFiles/main/session/session_show.h index ae764e1254..16f8e6f913 100644 --- a/Telegram/SourceFiles/main/session/session_show.h +++ b/Telegram/SourceFiles/main/session/session_show.h @@ -16,6 +16,9 @@ class Session; class SessionShow : public Ui::Show { public: [[nodiscard]] virtual Main::Session &session() const = 0; + + bool showFrozenError(); + }; [[nodiscard]] std::shared_ptr MakeSessionShow( diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 6fbf2c3eb0..08102a6bde 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -848,9 +848,10 @@ void Filler::addExportChat() { return; } const auto peer = _peer; + const auto navigation = _controller; _addAction( tr::lng_profile_export_chat(tr::now), - [=] { PeerMenuExportChat(peer); }, + [=] { PeerMenuExportChat(navigation, peer); }, &st::menuIconExport); } @@ -1516,7 +1517,9 @@ void Filler::fillSavedSublistActions() { } // namespace -void PeerMenuExportChat(not_null peer) { +void PeerMenuExportChat( + not_null controller, + not_null peer) { base::call_delayed(st::defaultPopupMenu.showDuration, [=] { Core::App().exportManager().start(peer); }); @@ -3126,14 +3129,20 @@ Fn ClearHistoryHandler( not_null controller, not_null peer) { return [=] { - controller->show(Box(peer, true)); + if (!controller->showFrozenError()) { + controller->show(Box(peer, true)); + } }; } Fn DeleteAndLeaveHandler( not_null controller, not_null peer) { - return [=] { controller->show(Box(DeleteChatBox, peer)); }; + return [=] { + if (!controller->showFrozenError()) { + controller->show(Box(DeleteChatBox, peer)); + } + }; } void FillDialogsEntryMenu( diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 5ad0c50813..e2406d8ee7 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -89,7 +89,9 @@ void MenuAddMarkAsReadChatListAction( const PeerMenuCallback &addAction, Fn customUnreadState = nullptr); -void PeerMenuExportChat(not_null peer); +void PeerMenuExportChat( + not_null controller, + not_null peer); void PeerMenuDeleteContact( not_null controller, not_null user); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a3fdd4d6d4..607077828c 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -364,6 +364,10 @@ Main::Session &SessionNavigation::session() const { return *_session; } +bool SessionNavigation::showFrozenError() { + return uiShow()->showFrozenError(); +} + void SessionNavigation::showPeerByLink(const PeerByLinkInfo &info) { Core::App().hideMediaView(); if (!info.phone.isEmpty()) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index afc847e3f8..c69dc06e9a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -180,6 +180,8 @@ public: [[nodiscard]] Main::Session &session() const; + bool showFrozenError(); + virtual void showSection( std::shared_ptr memento, const SectionShow ¶ms = SectionShow()) = 0; From b656e144533eebd8fe1132be1ca095d6cd4483af Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Mar 2025 10:44:08 +0400 Subject: [PATCH 098/122] Edit additional privacy options for gifts. --- Telegram/Resources/langs/lang.strings | 12 ++ .../SourceFiles/api/api_global_privacy.cpp | 92 ++++++++++-- Telegram/SourceFiles/api/api_global_privacy.h | 26 +++- Telegram/SourceFiles/settings/settings.style | 5 + .../settings/settings_privacy_controllers.cpp | 136 +++++++++++++++++- .../settings/settings_privacy_controllers.h | 10 ++ 6 files changed, 268 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6df4da1c10..7d6a1ff17d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1195,6 +1195,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_quick_dialog_action_delete" = "Delete"; "lng_settings_quick_dialog_action_disabled" = "Change folder"; +"lng_settings_generic_subscribe" = "Subscribe to {link} to use this setting."; +"lng_settings_generic_subscribe_link" = "Telegram Premium"; + "lng_sessions_header" = "This device"; "lng_sessions_other_header" = "Active Devices"; "lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; @@ -1310,6 +1313,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_gifts_always_title" = "Always allow"; "lng_edit_privacy_gifts_never_title" = "Never allow"; +"lng_edit_privacy_gifts_types" = "Accepted Gift Types"; +"lng_edit_privacy_gifts_premium" = "Premium Subscriptions"; +"lng_edit_privacy_gifts_unlimited" = "Unlimited"; +"lng_edit_privacy_gifts_limited" = "Limited-Edition"; +"lng_edit_privacy_gifts_unique" = "Unique"; +"lng_edit_privacy_gifts_types_about" = "Choose the types of gifts that you accept."; +"lng_edit_privacy_gifts_show_icon" = "Show Gift Icon in Chats"; +"lng_edit_privacy_gifts_show_icon_about" = "Display the {emoji}Gift icon in the message input field for both participants in all chats."; + "lng_edit_privacy_calls_title" = "Calls"; "lng_edit_privacy_calls_header" = "Who can call me"; "lng_edit_privacy_calls_always_empty" = "Always allow"; diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 01a6476c70..ffa85368d2 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -115,7 +115,9 @@ void GlobalPrivacy::updateHideReadTime(bool hide) { unarchiveOnNewMessageCurrent(), hide, newRequirePremiumCurrent(), - newChargeStarsCurrent()); + newChargeStarsCurrent(), + showGiftIconCurrent(), + disallowedGiftTypesCurrent()); } bool GlobalPrivacy::hideReadTimeCurrent() const { @@ -150,7 +152,39 @@ void GlobalPrivacy::updateMessagesPrivacy( unarchiveOnNewMessageCurrent(), hideReadTimeCurrent(), requirePremium, - chargeStars); + chargeStars, + showGiftIconCurrent(), + disallowedGiftTypesCurrent()); +} + +bool GlobalPrivacy::showGiftIconCurrent() const { + return _showGiftIcon.current(); +} + +rpl::producer GlobalPrivacy::showGiftIcon() const { + return _showGiftIcon.value(); +} + +DisallowedGiftTypes GlobalPrivacy::disallowedGiftTypesCurrent() const { + return _disallowedGiftTypes.current(); +} + +auto GlobalPrivacy::disallowedGiftTypes() const + -> rpl::producer { + return _disallowedGiftTypes.value(); +} + +void GlobalPrivacy::updateAdditionalGiftPrivacy( + DisallowedGiftTypes types, + bool showGiftIcon) { + update( + archiveAndMuteCurrent(), + unarchiveOnNewMessageCurrent(), + hideReadTimeCurrent(), + newRequirePremiumCurrent(), + newChargeStarsCurrent(), + showGiftIcon, + types); } void GlobalPrivacy::loadPaidReactionShownPeer() { @@ -182,7 +216,9 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) { unarchiveOnNewMessageCurrent(), hideReadTimeCurrent(), newRequirePremiumCurrent(), - newChargeStarsCurrent()); + newChargeStarsCurrent(), + showGiftIconCurrent(), + disallowedGiftTypesCurrent()); } void GlobalPrivacy::updateUnarchiveOnNewMessage( @@ -192,7 +228,9 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage( value, hideReadTimeCurrent(), newRequirePremiumCurrent(), - newChargeStarsCurrent()); + newChargeStarsCurrent(), + showGiftIconCurrent(), + disallowedGiftTypesCurrent()); } void GlobalPrivacy::update( @@ -200,8 +238,11 @@ void GlobalPrivacy::update( UnarchiveOnNewMessage unarchiveOnNewMessage, bool hideReadTime, bool newRequirePremium, - int newChargeStars) { + int newChargeStars, + bool showGiftIcon, + DisallowedGiftTypes disallowedGiftTypes) { using Flag = MTPDglobalPrivacySettings::Flag; + using DisallowedFlag = MTPDdisallowedStarGiftsSettings::Flag; _api.request(_requestId).cancel(); const auto newRequirePremiumAllowed = _session->premium() @@ -220,12 +261,27 @@ void GlobalPrivacy::update( | ((newRequirePremium && newRequirePremiumAllowed) ? Flag::f_new_noncontact_peers_require_premium : Flag()) - | Flag::f_noncontact_peers_paid_stars; + | Flag::f_noncontact_peers_paid_stars + | (showGiftIcon ? Flag::f_display_gifts_button : Flag()) + | Flag::f_disallowed_stargifts; + const auto disallowedFlags = DisallowedFlag() + | ((disallowedGiftTypes & DisallowedGiftType::Premium) + ? DisallowedFlag() AssertIsDebug() + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::Unlimited) + ? DisallowedFlag::f_disallow_unlimited_stargifts + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::Limited) + ? DisallowedFlag::f_disallow_limited_stargifts + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::Unique) + ? DisallowedFlag::f_disallow_unique_stargifts + : DisallowedFlag()); _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( MTP_globalPrivacySettings( MTP_flags(flags), MTP_long(newChargeStars), - MTPDisallowedStarGiftsSettings()) + MTP_disallowedStarGiftsSettings(MTP_flags(disallowedFlags))) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); @@ -237,7 +293,9 @@ void GlobalPrivacy::update( unarchiveOnNewMessage, hideReadTime, false, - 0); + 0, + false, + DisallowedGiftTypes()); } }).send(); _archiveAndMute = archiveAndMute; @@ -245,6 +303,8 @@ void GlobalPrivacy::update( _hideReadTime = hideReadTime; _newRequirePremium = newRequirePremium; _newChargeStars = newChargeStars; + _disallowedGiftTypes = disallowedGiftTypes; + _showGiftIcon = showGiftIcon; } void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) { @@ -258,6 +318,22 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) { _hideReadTime = data.is_hide_read_marks(); _newRequirePremium = data.is_new_noncontact_peers_require_premium(); _newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty(); + _showGiftIcon = data.is_display_gifts_button(); + if (const auto gifts = data.vdisallowed_stargifts()) { + const auto &data = gifts->data(); + _disallowedGiftTypes = DisallowedGiftType() + | (data.is_disallow_unlimited_stargifts() + ? DisallowedGiftType::Unlimited + : DisallowedGiftType()) + | (data.is_disallow_limited_stargifts() + ? DisallowedGiftType::Limited + : DisallowedGiftType()) + | (data.is_disallow_unique_stargifts() + ? DisallowedGiftType::Unique + : DisallowedGiftType()); + } else { + _disallowedGiftTypes = DisallowedGiftTypes(); + } } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index 6b0fc064b3..59b6d7860e 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/flags.h" #include "mtproto/sender.h" class ApiWrap; @@ -23,6 +24,16 @@ enum class UnarchiveOnNewMessage { AnyUnmuted, }; +enum class DisallowedGiftType { + Premium = 0x01, + Unlimited = 0x02, + Limited = 0x04, + Unique = 0x08, +}; +inline constexpr bool is_flag_type(DisallowedGiftType) { return true; } + +using DisallowedGiftTypes = base::flags; + [[nodiscard]] PeerId ParsePaidReactionShownPeer( not_null session, const MTPPaidReactionPrivacy &value); @@ -57,6 +68,15 @@ public: void updateMessagesPrivacy(bool requirePremium, int chargeStars); + [[nodiscard]] bool showGiftIconCurrent() const; + [[nodiscard]] rpl::producer showGiftIcon() const; + [[nodiscard]] DisallowedGiftTypes disallowedGiftTypesCurrent() const; + [[nodiscard]] auto disallowedGiftTypes() const + -> rpl::producer; + void updateAdditionalGiftPrivacy( + DisallowedGiftTypes types, + bool showGiftIcon); + void loadPaidReactionShownPeer(); void updatePaidReactionShownPeer(PeerId shownPeer); [[nodiscard]] PeerId paidReactionShownPeerCurrent() const; @@ -70,7 +90,9 @@ private: UnarchiveOnNewMessage unarchiveOnNewMessage, bool hideReadTime, bool newRequirePremium, - int newChargeStars); + int newChargeStars, + bool showGiftIcon, + DisallowedGiftTypes disallowedGiftTypes); const not_null _session; MTP::Sender _api; @@ -82,6 +104,8 @@ private: rpl::variable _hideReadTime = false; rpl::variable _newRequirePremium = false; rpl::variable _newChargeStars = 0; + rpl::variable _showGiftIcon = false; + rpl::variable _disallowedGiftTypes; rpl::variable _paidReactionShownPeer = false; std::vector> _callbacks; bool _paidReactionShownPeerLoaded = false; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index b866ec905a..22dee0c6f0 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -684,3 +684,8 @@ settingsChatLinkField: InputField(defaultInputField) { } settingsQuickDialogActionsTriggerFont: font(11px); + +settingsGiftIconEmoji: IconEmoji { + icon: icon{{ "menu/gift_premium", windowFg }}; + padding: margins(0px, 1px, 0px, 0px); +} diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index a843d7f046..e7e3363931 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -616,8 +616,8 @@ object_ptr PhoneNumberPrivacyController::setupMiddleWidget( } void PhoneNumberPrivacyController::saveAdditional() { - if (_saveAdditional) { - _saveAdditional(); + if (const auto onstack = _saveAdditional) { + onstack(); } } @@ -1296,8 +1296,8 @@ object_ptr ProfilePhotoPrivacyController::setupMiddleWidget( } void ProfilePhotoPrivacyController::saveAdditional() { - if (_saveAdditional) { - _saveAdditional(); + if (const auto onstack = _saveAdditional) { + onstack(); } } @@ -1626,4 +1626,132 @@ bool GiftsAutoSavePrivacyController::allowMiniAppsToggle( return true; } +object_ptr GiftsAutoSavePrivacyController::setupBelowWidget( + not_null controller, + not_null parent, + rpl::producer