diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a35291eaa..64ad8af50 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -546,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_workmode_tray" = "Show tray icon"; "lng_settings_workmode_window" = "Show taskbar icon"; "lng_settings_close_to_taskbar" = "Close to taskbar"; +"lng_settings_monochrome_icon" = "Use monochrome icon"; "lng_settings_window_system" = "Window title"; "lng_settings_title_chat_name" = "Show chat name"; "lng_settings_title_account_name" = "Show active account"; @@ -2684,6 +2685,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reply_options_quote" = "Update Quote"; "lng_reply_header_short" = "Reply"; "lng_reply_quote_selected" = "Quote Selected"; +"lng_reply_from_private_chat" = "This reply is from a private chat."; "lng_link_options_header" = "Link Preview Settings"; "lng_link_header_short" = "Link"; "lng_link_move_up" = "Move Up"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 1422235cb..cc75e489e 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.11.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 57cea3ebf..895e49214 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,1,0 - PRODUCTVERSION 4,11,1,0 + FILEVERSION 4,11,3,0 + PRODUCTVERSION 4,11,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "4.11.1.0" + VALUE "FileVersion", "4.11.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.11.1.0" + VALUE "ProductVersion", "4.11.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 82062cc15..6f0135c28 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,1,0 - PRODUCTVERSION 4,11,1,0 + FILEVERSION 4,11,3,0 + PRODUCTVERSION 4,11,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "4.11.1.0" + VALUE "FileVersion", "4.11.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.11.1.0" + VALUE "ProductVersion", "4.11.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 09001ae35..a0d6f4d95 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -270,7 +270,6 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } - const auto replyHeader = NewMessageReplyHeader(message.action); const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, message.action.options); InnerFillMessagePostFlags(message.action.options, peer, flags); @@ -399,7 +398,6 @@ void SendConfirmedFile( if (file->to.replyTo) { flags |= MessageFlag::HasReplyInfo; } - const auto replyHeader = NewMessageReplyHeader(action); const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, file->to.options); FillMessagePostFlags(action, peer, flags); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index ad6d137b9..93d289e76 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3391,7 +3391,6 @@ void ApiWrap::sendSharedContact( if (action.replyTo) { flags |= MessageFlag::HasReplyInfo; } - const auto replyHeader = NewMessageReplyHeader(action); FillMessagePostFlags(action, peer, flags); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -3614,14 +3613,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { action.generateLocal = true; sendAction(action); - const auto replyTo = action.replyTo.messageId - ? peer->owner().message(action.replyTo.messageId) - : nullptr; - const auto topicRootId = replyTo - ? replyTo->topicRootId() - : action.replyTo.topicRootId - ? action.replyTo.topicRootId - : Data::ForumTopic::kGeneralId; + const auto clearCloudDraft = action.clearDraft; + const auto topicRootId = action.replyTo.topicRootId; const auto topic = peer->forumTopicFor(topicRootId); if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer)) || Api::SendDice(message)) { @@ -3674,7 +3667,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) { const auto manualWebPage = exactWebPage && !ignoreWebPage && (message.webPage.manual || (isLast && !isFirst)); - const auto replyHeader = NewMessageReplyHeader(action); MTPMessageMedia media = MTP_messageMediaEmpty(); if (ignoreWebPage) { sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; @@ -3718,8 +3710,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_entities; mediaFlags |= MTPmessages_SendMedia::Flag::f_entities; } - const auto clearCloudDraft = action.clearDraft; - const auto topicRootId = action.replyTo.topicRootId; if (clearCloudDraft) { sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index c7b59f44b..84ea6a429 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -281,7 +281,7 @@ struct GiftCodeLink { [[nodiscard]] object_ptr MakePeerTableValue( not_null parent, - not_null controller, + not_null controller, PeerId id) { auto result = object_ptr(parent); const auto raw = result.data(); @@ -309,7 +309,7 @@ struct GiftCodeLink { label->setTextColorOverride(st::windowActiveTextFg->c); raw->setClickedCallback([=] { - controller->show(PrepareShortInfoBox(peer, controller)); + controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller)); }); return result; @@ -350,7 +350,7 @@ not_null AddTableRow( void AddTableRow( not_null table, rpl::producer label, - not_null controller, + not_null controller, PeerId id) { if (!id) { return; @@ -416,7 +416,7 @@ QString GiftDuration(int months) { void GiftCodeBox( not_null box, - not_null controller, + not_null controller, const QString &slug) { struct State { rpl::variable data; @@ -552,7 +552,7 @@ void GiftCodeBox( st::giveawayGiftCodeFooterMargin); footer->setClickHandlerFilter([=](const auto &...) { const auto chosen = [=](not_null thread) { - const auto content = controller->content(); + const auto content = controller->parentController()->content(); return content->shareUrl( thread, MakeGiftCodeLink(&controller->session(), slug).link, @@ -608,13 +608,13 @@ void GiftCodeBox( } void ResolveGiftCode( - not_null controller, + not_null controller, const QString &slug) { const auto done = [=](Api::GiftCode code) { if (!code) { controller->showToast(tr::lng_gift_link_expired(tr::now)); } else { - controller->show(Box(GiftCodeBox, controller, slug)); + controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug)); } }; controller->session().api().premium().checkGiftCode( @@ -624,7 +624,7 @@ void ResolveGiftCode( void GiveawayInfoBox( not_null box, - not_null controller, + not_null controller, Data::Giveaway giveaway, Api::GiveawayInfo info) { using State = Api::GiveawayState; @@ -784,7 +784,7 @@ void GiveawayInfoBox( } void ResolveGiveawayInfo( - not_null controller, + not_null controller, not_null peer, MsgId messageId, Data::Giveaway giveaway) { @@ -793,7 +793,7 @@ void ResolveGiveawayInfo( controller->showToast( tr::lng_confirm_phone_link_invalid(tr::now)); } else { - controller->show( + controller->uiShow()->showBox( Box(GiveawayInfoBox, controller, giveaway, info)); } }; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index 3f6d273bc..a1062dead 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -25,6 +25,7 @@ class GenericBox; namespace Window { class SessionController; +class SessionNavigation; } // namespace Window class GiftPremiumValidator final { @@ -47,14 +48,14 @@ private: void GiftCodeBox( not_null box, - not_null controller, + not_null controller, const QString &slug); void ResolveGiftCode( - not_null controller, + not_null controller, const QString &slug); void ResolveGiveawayInfo( - not_null controller, + not_null controller, not_null peer, MsgId messageId, Data::Giveaway giveaway); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 2c1e361fb..3395a5850 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -760,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth( nameWidth, outerWidth, { - .peer = _peer, + .peer = peer(), .verified = &(selected ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon), diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index d205ab4b0..d26140916 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -712,13 +712,24 @@ void Instance::destroyCurrentCall() { } } -bool Instance::hasActivePanel(not_null session) const { +bool Instance::hasVisiblePanel(Main::Session *session) const { if (inCall()) { - return (&_currentCall->user()->session() == session) - && _currentCallPanel->isActive(); + return _currentCallPanel->isVisible() + && (!session || (&_currentCall->user()->session() == session)); } else if (inGroupCall()) { - return (&_currentGroupCall->peer()->session() == session) - && _currentGroupCallPanel->isActive(); + return _currentGroupCallPanel->isVisible() + && (!session || (&_currentGroupCall->peer()->session() == session)); + } + return false; +} + +bool Instance::hasActivePanel(Main::Session *session) const { + if (inCall()) { + return _currentCallPanel->isActive() + && (!session || (&_currentCall->user()->session() == session)); + } else if (inGroupCall()) { + return _currentGroupCallPanel->isActive() + && (!session || (&_currentGroupCall->peer()->session() == session)); } return false; } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 4113a600e..e15d800be 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -89,8 +89,10 @@ public: [[nodiscard]] rpl::producer currentGroupCallValue() const; [[nodiscard]] bool inCall() const; [[nodiscard]] bool inGroupCall() const; + [[nodiscard]] bool hasVisiblePanel( + Main::Session *session = nullptr) const; [[nodiscard]] bool hasActivePanel( - not_null session) const; + Main::Session *session = nullptr) const; bool activateCurrentCall(const QString &joinHash = QString()); bool minimizeCurrentActiveCall(); bool toggleFullScreenCurrentActiveCall(); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 46481cad6..857596475 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -106,12 +106,15 @@ Panel::Panel(not_null call) Panel::~Panel() = default; -bool Panel::isActive() const { - return window()->isActiveWindow() - && window()->isVisible() +bool Panel::isVisible() const { + return window()->isVisible() && !(window()->windowState() & Qt::WindowMinimized); } +bool Panel::isActive() const { + return window()->isActiveWindow() && isVisible(); +} + void Panel::showAndActivate() { if (window()->isHidden()) { window()->show(); diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index 16836db62..dc715584a 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -61,6 +61,7 @@ public: Panel(not_null call); ~Panel(); + [[nodiscard]] bool isVisible() const; [[nodiscard]] bool isActive() const; void showAndActivate(); void minimize(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 6002963a9..d3bac6aeb 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -258,12 +258,15 @@ not_null Panel::call() const { return _call; } -bool Panel::isActive() const { - return window()->isActiveWindow() - && window()->isVisible() +bool Panel::isVisible() const { + return window()->isVisible() && !(window()->windowState() & Qt::WindowMinimized); } +bool Panel::isActive() const { + return window()->isActiveWindow() && isVisible(); +} + base::weak_ptr Panel::showToast( const QString &text, crl::time duration) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index cf9352697..851cc91d8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -91,6 +91,7 @@ public: [[nodiscard]] not_null widget() const; [[nodiscard]] not_null call() const; + [[nodiscard]] bool isVisible() const; [[nodiscard]] bool isActive() const; base::weak_ptr showToast( diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 9c5b37751..475e87cb3 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -493,10 +493,7 @@ InlineBotQuery ParseInlineBotQuery( result.lookingUpBot = true; } } - if (result.lookingUpBot) { - result.query = QString(); - return result; - } else if (result.bot + if (result.bot && (!result.bot->isBot() || result.bot->botInfo->inlinePlaceholder.isEmpty())) { result.bot = nullptr; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 156cc4ed0..428ab38c3 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -348,6 +348,8 @@ QByteArray Settings::serialize() const { for (const auto &id : _recentEmojiSkip) { stream << id; } + stream + << qint32(_trayIconMonochrome.current() ? 1 : 0); } return result; } @@ -457,6 +459,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0); qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0; base::flat_set recentEmojiSkip; + qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0); stream >> themesAccentColors; if (!stream.atEnd()) { @@ -707,6 +710,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) { } } } + if (!stream.atEnd()) { + stream >> trayIconMonochrome; + } else { + // Let existing clients use the old value. + trayIconMonochrome = 0; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -901,6 +910,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional(); _storiesClickTooltipHidden = (storiesClickTooltipHidden == 1); _recentEmojiSkip = std::move(recentEmojiSkip); + _trayIconMonochrome = (trayIconMonochrome == 1); } QString Settings::getSoundPath(const QString &key) const { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index ea5fb1656..c06f9d817 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -708,6 +708,15 @@ public: [[nodiscard]] rpl::producer closeToTaskbarChanges() const { return _closeToTaskbar.changes(); } + void setTrayIconMonochrome(bool value) { + _trayIconMonochrome = value; + } + [[nodiscard]] bool trayIconMonochrome() const { + return _trayIconMonochrome.current(); + } + [[nodiscard]] rpl::producer trayIconMonochromeChanges() const { + return _trayIconMonochrome.changes(); + } void setCustomDeviceModel(const QString &model) { _customDeviceModel = model; @@ -924,6 +933,7 @@ private: rpl::variable _workMode = WorkMode::WindowAndTray; base::flags _hiddenGroupCallTooltips; rpl::variable _closeToTaskbar = false; + rpl::variable _trayIconMonochrome = true; rpl::variable _customDeviceModel; rpl::variable _playerRepeatMode; rpl::variable _playerOrderMode; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 0aadab082..8b121852e 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppFile = "AyuGram"_cs; -constexpr auto AppVersion = 4011001; -constexpr auto AppVersionStr = "4.11.1"; +constexpr auto AppVersion = 4011003; +constexpr auto AppVersionStr = "4.11.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 66bf9d540..298dae217 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/notifications_manager.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/view/history_view_element.h" #include "core/application.h" #include "apiwrap.h" @@ -52,8 +53,18 @@ MTPInputReplyTo ReplyToForMTP( } } } else if (replyTo.messageId || replyTo.topicRootId) { + const auto to = LookupReplyTo(history, replyTo.messageId); + const auto replyingToTopicId = replyTo.topicRootId + ? replyTo.topicRootId + : Data::ForumTopic::kGeneralId; + const auto replyToTopicId = !to + ? replyingToTopicId + : to->topicRootId() + ? to->topicRootId() + : Data::ForumTopic::kGeneralId; const auto external = replyTo.messageId - && (replyTo.messageId.peer != history->peer->id); + && (replyTo.messageId.peer != history->peer->id + || replyingToTopicId != replyToTopicId); const auto quoteEntities = Api::EntitiesToMTP( &history->session(), replyTo.quote.entities, diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 9cf49244a..cb0278a27 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -538,6 +538,7 @@ ItemPreview Media::toGroupPreview( auto videoCount = 0; auto audioCount = 0; auto fileCount = 0; + auto manyCaptions = false; for (const auto &item : items) { if (const auto media = item->media()) { if (media->photo()) { @@ -571,12 +572,12 @@ ItemPreview Media::toGroupPreview( if (result.text.text.isEmpty()) { result.text = original; } else { - result.text = {}; + manyCaptions = true; } } } } - if (result.text.text.isEmpty()) { + if (manyCaptions || result.text.text.isEmpty()) { const auto mediaCount = photoCount + videoCount; auto genericText = (photoCount && videoCount) ? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount) 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 deb786a28..89a2e6b49 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -579,11 +579,6 @@ bool InnerWidget::elementUnderCursor( return (Element::Hovered() == view); } -float64 InnerWidget::elementHighlightOpacity( - not_null item) const { - return 0.; -} - bool InnerWidget::elementInSelectionMode() { return false; } 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 36c321810..da7653b1b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -93,8 +93,6 @@ public: HistoryView::Context elementContext() override; bool elementUnderCursor( not_null view) override; - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2657d872a..6059be072 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -207,10 +207,6 @@ public: not_null view) override { return (Element::Moused() == view); } - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override { - return _widget ? _widget->elementHighlightOpacity(item) : 0.; - } bool elementInSelectionMode() override { return _widget ? _widget->inSelectionMode() : false; } @@ -1065,6 +1061,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto clip = e->rect(); auto context = preparePaintContext(clip); + context.highlightPathCache = &_highlightPathCache; _pathGradient->startFrame( 0, width(), @@ -1148,7 +1145,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } else if (item->isUnreadMention() && !item->isUnreadMedia()) { readContents.insert(item); - _widget->enqueueMessageHighlight(view); + _widget->enqueueMessageHighlight(view, {}); } } session().data().reactions().poll(item, context.now); @@ -1190,6 +1187,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { view, selfromy - mtop, seltoy - mtop); + context.highlight = _widget->itemHighlight(view->data()); view->draw(p, context); processPainted(view, top, height); @@ -1224,9 +1222,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) { const auto &sendingAnimation = _controller->sendingAnimation(); while (top < drawToY) { const auto height = view->height(); + const auto item = view->data(); if ((context.clip.y() < height) && (hdrawtop < top + height) - && !sendingAnimation.hasAnimatedMessage(view->data())) { + && !sendingAnimation.hasAnimatedMessage(item)) { context.reactionInfo = _reactionsManager->currentReactionPaintInfo(); context.outbg = view->hasOutLayout(); @@ -1234,6 +1233,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { view, selfromy - htop, seltoy - htop); + context.highlight = _widget->itemHighlight(item); view->draw(p, context); processPainted(view, top, height); } @@ -1678,7 +1678,10 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but Ui::MarkInactivePress(_controller->widget(), false); } - if (ClickHandler::getPressed()) { + const auto pressed = ClickHandler::getPressed(); + if (pressed + && (!Element::Hovered() + || !Element::Hovered()->allowTextSelectionByHandler(pressed))) { _mouseAction = MouseAction::PrepareDrag; } else if (inSelectionMode()) { if (_dragStateItem @@ -2111,7 +2114,7 @@ void HistoryInner::toggleFavoriteReaction(not_null view) const { item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick); } -TextWithEntities HistoryInner::selectedQuote( +HistoryView::SelectedQuote HistoryInner::selectedQuote( not_null item) const { if (_selected.size() != 1 || _selected.begin()->first != item @@ -2401,25 +2404,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : (Data::CanSendAnything(peer) && (!peer->isChannel() || peer->asChannel()->amIn())); }(); - const auto canReply = canSendReply || [&] { - const auto peer = item->history()->peer; - if (const auto chat = peer->asChat()) { - return !chat->isForbidden(); - } else if (const auto channel = peer->asChannel()) { - return !channel->isForbidden(); - } - return true; - }(); + const auto canReply = canSendReply || item->allowsForward(); if (canReply) { - const auto itemId = item->fullId(); - const auto quote = selectedQuote(item); - auto text = quote.empty() - ? tr::lng_context_reply_msg(tr::now) - : tr::lng_context_quote_and_reply(tr::now); + const auto selected = selectedQuote(item); + auto text = selected + ? tr::lng_context_quote_and_reply(tr::now) + : tr::lng_context_reply_msg(tr::now); + const auto replyToItem = selected.item ? selected.item : item; + const auto itemId = replyToItem->fullId(); + const auto quote = selected.text; text.replace('&', u"&&"_q); _menu->addAction(text, [=] { if (canSendReply) { _widget->replyToMessage({ itemId, quote }); + if (!quote.empty()) { + _widget->clearSelected(); + } } else { HistoryView::Controls::ShowReplyToChatBox( controller->uiShow(), @@ -3484,11 +3484,6 @@ void HistoryInner::elementStartStickerLoop( _animatedStickersPlayed.emplace(view->data()); } -float64 HistoryInner::elementHighlightOpacity( - not_null item) const { - return _widget->highlightOpacity(item); -} - void HistoryInner::elementShowPollResults( not_null poll, FullMsgId context) { @@ -3855,6 +3850,10 @@ void HistoryInner::mouseActionUpdate() { selState = view->adjustSelection(selState, _mouseSelectType); } } + if (!selState.empty()) { + // We started selecting text in web page preview. + ClickHandler::unpressed(); + } if (_selected[_mouseActionItem] != selState) { _selected[_mouseActionItem] = selState; repaintItem(_mouseActionItem); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 981b9e424..810521f50 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "history/view/history_view_top_bar_widget.h" +#include + struct ClickContext; struct ClickHandlerContext; @@ -33,6 +35,7 @@ class EmptyPainter; class Element; class TranslateTracker; struct PinnedId; +struct SelectedQuote; } // namespace HistoryView namespace HistoryView::Reactions { @@ -136,8 +139,6 @@ public: int from, int till) const; void elementStartStickerLoop(not_null view); - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const; void elementShowPollResults( not_null poll, FullMsgId context); @@ -314,7 +315,7 @@ private: QPoint mapPointToItem(QPoint p, const Element *view) const; QPoint mapPointToItem(QPoint p, const HistoryItem *item) const; - [[nodiscard]] TextWithEntities selectedQuote( + [[nodiscard]] HistoryView::SelectedQuote selectedQuote( not_null item) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); @@ -462,6 +463,7 @@ private: std::optional _chooseForReportReason; const std::unique_ptr _pathGradient; + QPainterPath _highlightPathCache; bool _isChatWide = false; base::flat_set> _animatedStickersPlayed; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 61ca6e17f..9199fa370 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -470,6 +470,14 @@ HistoryItem::HistoryItem( const auto dropForwardInfo = original->computeDropForwardedInfo(); config.reply.messageId = config.reply.topMessageId = topicRootId; config.reply.topicPost = (topicRootId != 0); + if (const auto originalReply = original->Get()) { + if (originalReply->external()) { + config.reply = originalReply->fields(); + if (!config.reply.externalPeerId) { + config.reply.messageId = 0; + } + } + } if (!dropForwardInfo) { config.originalDate = original->originalDate(); if (const auto info = original->hiddenSenderInfo()) { @@ -3389,16 +3397,28 @@ void HistoryItem::createComponentsHelper( ? replyTo.messageId.peer : PeerId(); const auto to = LookupReplyTo(_history, replyTo.messageId); - const auto replyToTop = LookupReplyToTop(_history, to); + const auto replyToTop = replyTo.topicRootId + ? replyTo.topicRootId + : LookupReplyToTop(_history, to); config.reply.topMessageId = replyToTop ? replyToTop : (replyTo.messageId.peer == history()->peer->id) ? replyTo.messageId.msg : MsgId(); + if (!config.reply.externalPeerId + && to + && config.reply.topicPost + && replyTo.topicRootId != to->topicRootId()) { + config.reply.externalPeerId = replyTo.messageId.peer; + } const auto forum = _history->asForum(); - config.reply.topicPost = LookupReplyIsTopicPost(to) - || (to && to->Has()) - || (forum && forum->creating(config.reply.topMessageId)); + config.reply.topicPost = config.reply.externalPeerId + ? (replyTo.topicRootId + && (replyTo.topicRootId != Data::ForumTopic::kGeneralId)) + : (LookupReplyIsTopicPost(to) + || (to && to->Has()) + || (forum && forum->creating(config.reply.topMessageId))); + config.reply.manualQuote = !replyTo.quote.empty(); config.reply.quote = std::move(replyTo.quote); } config.markup = std::move(markup); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 67dcd9b92..c9f01a2c0 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -397,9 +397,6 @@ ReplyFields ReplyFieldsFromMTP( auto result = ReplyFields(); if (const auto peer = data.vreply_to_peer_id()) { result.externalPeerId = peerFromMTP(*peer); - if (result.externalPeerId == history->peer->id) { - result.externalPeerId = 0; - } } const auto owner = &history->owner(); if (const auto id = data.vreply_to_msg_id().value_or_empty()) { @@ -426,6 +423,7 @@ ReplyFields ReplyFieldsFromMTP( &owner->session(), data.vquote_entities().value_or_empty()), }; + result.manualQuote = data.is_quote(); return result; }, [&](const MTPDmessageReplyStoryHeader &data) { return ReplyFields{ @@ -525,8 +523,7 @@ bool HistoryMessageReply::updateData( } } - const auto external = _fields.externalSenderId - || !_fields.externalSenderName.isEmpty(); + const auto external = this->external(); if (resolvedMessage || resolvedStory || (external && (!_fields.messageId || force))) { @@ -624,13 +621,15 @@ void HistoryMessageReply::setLinkFrom( if (externalPeerId) { controller->showPeerInfo( controller->session().data().peer(externalPeerId)); - } else { - controller->showToast(u"External reply"_q); } + controller->showToast(tr::lng_reply_from_private_chat(tr::now)); } }; _link = resolvedMessage - ? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId()) + ? JumpToMessageClickHandler( + resolvedMessage.get(), + holder->fullId(), + _fields.manualQuote ? _fields.quote : TextWithEntities()) : resolvedStory ? JumpToStoryClickHandler(resolvedStory.get()) : (external && !_fields.messageId) @@ -656,10 +655,18 @@ void HistoryMessageReply::clearData(not_null holder) { resolvedStory.get()); resolvedStory = nullptr; } + _name.clear(); + _text.clear(); _unavailable = 1; refreshReplyToMedia(); } +bool HistoryMessageReply::external() const { + return _fields.externalPeerId + || _fields.externalSenderId + || !_fields.externalSenderName.isEmpty(); +} + PeerData *HistoryMessageReply::sender(not_null holder) const { if (resolvedStory) { return resolvedStory->peer(); @@ -832,7 +839,7 @@ void HistoryMessageReply::paint( y += st::historyReplyTop; const auto rect = QRect(x, y, w, _height); - const auto hasQuote = !_fields.quote.empty(); + const auto hasQuote = _fields.manualQuote && !_fields.quote.empty(); const auto selected = context.selected(); const auto colorPeer = resolvedMessage ? resolvedMessage->displayFrom() diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index c3bf23770..c250e91fb 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -239,6 +239,7 @@ struct ReplyFields { MsgId topMessageId = 0; StoryId storyId = 0; bool topicPost = false; + bool manualQuote = false; }; [[nodiscard]] ReplyFields ReplyFieldsFromMTP( @@ -273,6 +274,7 @@ struct HistoryMessageReply // Must be called before destructor. void clearData(not_null holder); + [[nodiscard]] bool external() const; [[nodiscard]] PeerData *sender(not_null holder) const; [[nodiscard]] QString senderName(not_null holder) const; [[nodiscard]] QString senderName(not_null peer) const; @@ -300,6 +302,9 @@ struct HistoryMessageReply bool inBubble) const; void unloadPersistentAnimation(); + [[nodiscard]] ReplyFields fields() const { + return _fields; + } [[nodiscard]] PeerId externalPeerId() const { return _fields.externalPeerId; } @@ -321,6 +326,9 @@ struct HistoryMessageReply [[nodiscard]] bool topicPost() const { return _fields.topicPost; } + [[nodiscard]] bool manualQuote() const { + return _fields.manualQuote; + } [[nodiscard]] QString statePhrase() const; void setLinkFrom(not_null holder); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index f30eb32d9..375a6ad7b 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_helpers.h" +#include "api/api_text_entities.h" #include "calls/calls_instance.h" #include "data/notify/data_notify_settings.h" #include "data/data_chat_participant_status.h" @@ -202,6 +203,14 @@ MsgId LookupReplyToTop(not_null history, HistoryItem *replyTo) { : 0; } +MsgId LookupReplyToTop(not_null history, FullReplyTo replyTo) { + return replyTo.topicRootId + ? replyTo.topicRootId + : LookupReplyToTop( + history, + LookupReplyTo(history, replyTo.messageId)); +} + bool LookupReplyIsTopicPost(HistoryItem *replyTo) { return replyTo && (replyTo->topicRootId() != Data::ForumTopic::kGeneralId); @@ -259,17 +268,20 @@ bool IsItemScheduledUntilOnline(not_null item) { ClickHandlerPtr JumpToMessageClickHandler( not_null item, - FullMsgId returnToId) { + FullMsgId returnToId, + TextWithEntities highlightPart) { return JumpToMessageClickHandler( item->history()->peer, item->id, - returnToId); + returnToId, + std::move(highlightPart)); } ClickHandlerPtr JumpToMessageClickHandler( not_null peer, MsgId msgId, - FullMsgId returnToId) { + FullMsgId returnToId, + TextWithEntities highlightPart) { return std::make_shared([=] { const auto separate = Core::App().separateWindowForPeer(peer); const auto controller = separate @@ -279,6 +291,7 @@ ClickHandlerPtr JumpToMessageClickHandler( auto params = Window::SectionShow{ Window::SectionShow::Way::Forward }; + params.highlightPart = highlightPart; params.origin = Window::SectionShow::OriginMessage{ returnToId }; @@ -369,19 +382,27 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { const auto externalPeerId = (replyTo.messageId.peer == historyPeer) ? PeerId() : replyTo.messageId.peer; - const auto to = LookupReplyTo(action.history, replyTo.messageId); - const auto replyToTop = LookupReplyToTop(action.history, to); + const auto replyToTop = LookupReplyToTop(action.history, replyTo); + auto quoteEntities = Api::EntitiesToMTP( + &action.history->session(), + replyTo.quote.entities, + Api::ConvertOption::SkipLocal); return MTP_messageReplyHeader( MTP_flags(Flag::f_reply_to_msg_id | (replyToTop ? Flag::f_reply_to_top_id : Flag()) - | (externalPeerId ? Flag::f_reply_to_peer_id : Flag())), + | (externalPeerId ? Flag::f_reply_to_peer_id : Flag()) + | (replyTo.quote.empty() ? Flag() : Flag::f_quote) + | (replyTo.quote.empty() ? Flag() : Flag::f_quote_text) + | (quoteEntities.v.empty() + ? Flag() + : Flag::f_quote_entities)), MTP_int(replyTo.messageId.msg), peerToMTP(externalPeerId), MTPMessageFwdHeader(), // reply_from MTPMessageMedia(), // reply_media - MTP_int(replyToTop), // reply_to_top_id - MTPstring(), // quote_text - MTPVector()); // quote_entities + MTP_int(replyToTop), + MTP_string(replyTo.quote.text), + quoteEntities); } return MTPMessageReplyHeader(); } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 8dd151274..cf1045abd 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -89,6 +89,9 @@ void RequestDependentMessageStory( [[nodiscard]] MsgId LookupReplyToTop( not_null history, HistoryItem *replyTo); +[[nodiscard]] MsgId LookupReplyToTop( + not_null history, + FullReplyTo replyTo); [[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo); struct SendingErrorRequest { @@ -120,10 +123,12 @@ struct SendingErrorRequest { [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( not_null peer, MsgId msgId, - FullMsgId returnToId = FullMsgId()); + FullMsgId returnToId = FullMsgId(), + TextWithEntities highlightPart = {}); [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( not_null item, - FullMsgId returnToId = FullMsgId()); + FullMsgId returnToId = FullMsgId(), + TextWithEntities highlightPart = {}); [[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler( not_null story); ClickHandlerPtr JumpToStoryClickHandler( diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index 939e56980..29e157344 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -8,14 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_view_highlight_manager.h" #include "data/data_session.h" +#include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" +#include "ui/chat/chat_style.h" namespace HistoryView { -constexpr auto kAnimationFirstPart = st::activeFadeInDuration - / float64(st::activeFadeInDuration + st::activeFadeOutDuration); - ElementHighlighter::ElementHighlighter( not_null data, ViewForItem viewForItem, @@ -26,63 +25,95 @@ ElementHighlighter::ElementHighlighter( , _animation(*this) { } -void ElementHighlighter::enqueue(not_null view) { - const auto item = view->data(); - const auto fullId = item->fullId(); +void ElementHighlighter::enqueue( + not_null view, + const TextWithEntities &part) { + const auto data = computeHighlight(view, part); if (_queue.empty() && !_animation.animating()) { - highlight(fullId); - } else if (_highlightedMessageId != fullId - && !base::contains(_queue, fullId)) { - _queue.push_back(fullId); + highlight(data); + } else if (_highlighted != data && !base::contains(_queue, data)) { + _queue.push_back(data); checkNextHighlight(); } } +void ElementHighlighter::highlight( + not_null view, + const TextWithEntities &part) { + highlight(computeHighlight(view, part)); +} + void ElementHighlighter::checkNextHighlight() { if (_animation.animating()) { return; } - const auto nextHighlight = [&] { + const auto next = [&] { while (!_queue.empty()) { - const auto fullId = _queue.front(); + const auto highlight = _queue.front(); _queue.pop_front(); - if (const auto item = _data->message(fullId)) { + if (const auto item = _data->message(highlight.itemId)) { if (_viewForItem(item)) { - return fullId; + return highlight; } } } - return FullMsgId(); + return Highlight(); }(); - if (!nextHighlight) { - return; + if (next) { + highlight(next); } - highlight(nextHighlight); } -float64 ElementHighlighter::progress( +Ui::ChatPaintHighlight ElementHighlighter::state( not_null item) const { - if (item->fullId() == _highlightedMessageId) { - const auto progress = _animation.progress(); - return std::min(progress / kAnimationFirstPart, 1.) - - ((progress - kAnimationFirstPart) / (1. - kAnimationFirstPart)); + if (item->fullId() == _highlighted.itemId) { + auto result = _animation.state(); + result.range = _highlighted.part; + return result; } - return 0.; + return {}; } -void ElementHighlighter::highlight(FullMsgId itemId) { - if (const auto item = _data->message(itemId)) { +ElementHighlighter::Highlight ElementHighlighter::computeHighlight( + not_null view, + const TextWithEntities &part) { + const auto item = view->data(); + const auto owner = &item->history()->owner(); + if (const auto group = owner->groups().find(item)) { + const auto leader = group->items.front(); + const auto leaderId = leader->fullId(); + const auto i = ranges::find(group->items, item); + if (i != end(group->items)) { + const auto index = int(i - begin(group->items)); + if (part.empty()) { + return { leaderId, AddGroupItemSelection({}, index) }; + } else if (const auto leaderView = _viewForItem(leader)) { + return { + leaderId, + leaderView->selectionFromQuote(item, part), + }; + } + } + return { leaderId }; + } else if (part.empty()) { + return { item->fullId() }; + } + return { item->fullId(), view->selectionFromQuote(item, part) }; +} + +void ElementHighlighter::highlight(Highlight data) { + if (const auto item = _data->message(data.itemId)) { if (const auto view = _viewForItem(item)) { - if (_highlightedMessageId - && _highlightedMessageId != itemId) { - if (const auto was = _data->message(_highlightedMessageId)) { + if (_highlighted && _highlighted.itemId != data.itemId) { + if (const auto was = _data->message(_highlighted.itemId)) { if (const auto view = _viewForItem(was)) { repaintHighlightedItem(view); } } } - _highlightedMessageId = itemId; - _animation.start(); + _highlighted = data; + _animation.start(!data.part.empty() + && !IsSubGroupSelection(data.part)); repaintHighlightedItem(view); } @@ -105,7 +136,7 @@ void ElementHighlighter::repaintHighlightedItem( } void ElementHighlighter::updateMessage() { - if (const auto item = _data->message(_highlightedMessageId)) { + if (const auto item = _data->message(_highlighted.itemId)) { if (const auto view = _viewForItem(item)) { repaintHighlightedItem(view); } @@ -114,7 +145,7 @@ void ElementHighlighter::updateMessage() { void ElementHighlighter::clear() { _animation.cancel(); - _highlightedMessageId = FullMsgId(); + _highlighted = {}; _lastHighlightedMessageId = FullMsgId(); _queue.clear(); } @@ -125,60 +156,117 @@ ElementHighlighter::AnimationManager::AnimationManager( } bool ElementHighlighter::AnimationManager::animating() const { - if (anim::Disabled()) { - return (_timer && _timer->isActive()); - } else { + if (_timer && _timer->isActive()) { + return true; + } else if (!anim::Disabled()) { return _simple.animating(); } + return false; } -float64 ElementHighlighter::AnimationManager::progress() const { +Ui::ChatPaintHighlight ElementHighlighter::AnimationManager::state() const { if (anim::Disabled()) { - return (_timer && _timer->isActive()) ? kAnimationFirstPart : 0.; - } else { - return _simple.value(0.); + return { + .opacity = !_timer ? 0. : 1., + .collapsion = !_timer ? 0. : _fadingOut ? 1. : 0., + }; } + return { + .opacity = ((!_fadingOut && _collapsing) + ? 1. + : _simple.value(_fadingOut ? 0. : 1.)), + .collapsion = ((!_withTextPart || !_collapsing) + ? 0. + : _fadingOut + ? 1. + : _simple.value(1.)), + }; } MsgId ElementHighlighter::latestSingleHighlightedMsgId() const { - return _highlightedMessageId - ? _highlightedMessageId.msg + return _highlighted.itemId + ? _highlighted.itemId.msg : _lastHighlightedMessageId.msg; } -void ElementHighlighter::AnimationManager::start() { +void ElementHighlighter::AnimationManager::start(bool withTextPart) { + _withTextPart = withTextPart; const auto finish = [=] { cancel(); _parent._lastHighlightedMessageId = base::take( - _parent._highlightedMessageId); + _parent._highlighted.itemId); _parent.checkNextHighlight(); }; cancel(); if (anim::Disabled()) { _timer.emplace([=] { _parent.updateMessage(); - finish(); + if (_withTextPart && !_fadingOut) { + _fadingOut = true; + _timer->callOnce(st::activeFadeOutDuration); + } else { + finish(); + } }); - _timer->callOnce(st::activeFadeOutDuration); + _timer->callOnce(_withTextPart + ? st::activeFadeInDuration + : st::activeFadeOutDuration); _parent.updateMessage(); } else { - const auto to = 1.; _simple.start( [=](float64 value) { _parent.updateMessage(); - if (value == to) { - finish(); + if (value == 1.) { + if (_withTextPart) { + _timer.emplace([=] { + _parent.updateMessage(); + if (_collapsing) { + _fadingOut = true; + } else { + _collapsing = true; + } + _simple.start([=](float64 value) { + _parent.updateMessage(); + if (_fadingOut && value == 0.) { + finish(); + } else if (!_fadingOut && value == 1.) { + _timer->callOnce( + st::activeFadeOutDuration); + } + }, + _fadingOut ? 1. : 0., + _fadingOut ? 0. : 1., + (_fadingOut + ? st::activeFadeInDuration + : st::fadeWrapDuration)); + }); + _timer->callOnce(st::activeFadeInDuration); + } else { + _fadingOut = true; + _simple.start([=](float64 value) { + _parent.updateMessage(); + if (value == 0.) { + finish(); + } + }, + 1., + 0., + st::activeFadeOutDuration); + } } }, 0., - to, - st::activeFadeInDuration + st::activeFadeOutDuration); + 1., + st::activeFadeInDuration); } } void ElementHighlighter::AnimationManager::cancel() { _simple.stop(); _timer.reset(); + _fadingOut = false; + _collapsed = false; + _collapsing = false; } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.h b/Telegram/SourceFiles/history/history_view_highlight_manager.h index 43372a8b9..7fdcdfc0b 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.h +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.h @@ -16,6 +16,10 @@ namespace Data { class Session; } // namespace Data +namespace Ui { +struct ChatPaintHighlight; +} // namespace Ui + namespace HistoryView { class Element; @@ -29,40 +33,59 @@ public: ViewForItem viewForItem, RepaintView repaintView); - void enqueue(not_null view); - void highlight(FullMsgId itemId); + void enqueue(not_null view, const TextWithEntities &part); + void highlight(not_null view, const TextWithEntities &part); void clear(); - [[nodiscard]] float64 progress(not_null item) const; + [[nodiscard]] Ui::ChatPaintHighlight state( + not_null item) const; [[nodiscard]] MsgId latestSingleHighlightedMsgId() const; private: - void checkNextHighlight(); - void repaintHighlightedItem(not_null view); - void updateMessage(); - class AnimationManager final { public: AnimationManager(ElementHighlighter &parent); [[nodiscard]] bool animating() const; - [[nodiscard]] float64 progress() const; - void start(); + [[nodiscard]] Ui::ChatPaintHighlight state() const; + void start(bool withTextPart); void cancel(); private: ElementHighlighter &_parent; Ui::Animations::Simple _simple; std::optional _timer; + bool _withTextPart = false; + bool _collapsing = false; + bool _collapsed = false; + bool _fadingOut = false; }; + struct Highlight { + FullMsgId itemId; + TextSelection part; + + explicit operator bool() const { + return itemId.operator bool(); + } + friend inline bool operator==(Highlight, Highlight) = default; + }; + + [[nodiscard]] Highlight computeHighlight( + not_null view, + const TextWithEntities &part); + void highlight(Highlight data); + void checkNextHighlight(); + void repaintHighlightedItem(not_null view); + void updateMessage(); + const not_null _data; const ViewForItem _viewForItem; const RepaintView _repaintView; - FullMsgId _highlightedMessageId; + Highlight _highlighted; FullMsgId _lastHighlightedMessageId; - std::deque _queue; + std::deque _queue; AnimationManager _animation; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 059e97a73..c646a9b40 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -847,7 +847,9 @@ HistoryWidget::HistoryWidget( }); } else { fastShowAtEnd(action.history); - if (cancelReply(lastKeyboardUsed) && !action.clearDraft) { + if (!_justMarkingAsRead + && cancelReply(lastKeyboardUsed) + && !action.clearDraft) { saveCloudDraft(); } } @@ -1077,7 +1079,7 @@ void HistoryWidget::initTabbedSelector() { if (!data.recipientOverride) { return true; } else if (data.recipientOverride != _peer) { - showHistory(data.recipientOverride->id, ShowAtTheEndMsgId); + showHistory(data.recipientOverride->id, ShowAtTheEndMsgId, {}); } return (data.recipientOverride == _peer); }) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) { @@ -1272,13 +1274,14 @@ void HistoryWidget::scrollToAnimationCallback( } void HistoryWidget::enqueueMessageHighlight( - not_null view) { - _highlighter.enqueue(view); + not_null view, + const TextWithEntities &part) { + _highlighter.enqueue(view, part); } -float64 HistoryWidget::highlightOpacity( +Ui::ChatPaintHighlight HistoryWidget::itemHighlight( not_null item) const { - return _highlighter.progress(item); + return _highlighter.state(item); } int HistoryWidget::itemTopForHighlight( @@ -1395,9 +1398,7 @@ void HistoryWidget::updateInlineBotQuery() { _inlineBotResolveRequestId = _api.request(MTPcontacts_ResolveUsername( MTP_string(username) )).done([=](const MTPcontacts_ResolvedPeer &result) { - Expects(result.type() == mtpc_contacts_resolvedPeer); - - const auto &data = result.c_contacts_resolvedPeer(); + const auto &data = result.data(); const auto resolvedBot = [&]() -> UserData* { if (const auto user = session().data().processUsers( data.vusers())) { @@ -1976,9 +1977,10 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const { void HistoryWidget::showHistory( const PeerId &peerId, MsgId showAtMsgId, - bool reload) { + const TextWithEntities &highlightPart) { _pinnedClickedId = FullMsgId(); _minPinnedId = std::nullopt; + _showAtMsgHighlightPart = {}; const auto wasDialogsEntryState = computeDialogsEntryState(); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); @@ -1990,7 +1992,7 @@ void HistoryWidget::showHistory( controller()->sendingAnimation().clear(); _topToast.hide(anim::type::instant); if (_history) { - if (_peer->id == peerId && !reload) { + if (_peer->id == peerId) { updateForwarding(); if (showAtMsgId == ShowAtUnreadMsgId @@ -2026,10 +2028,10 @@ void HistoryWidget::showHistory( ).arg(_history->inboxReadTillId().bare ).arg(Logs::b(_history->loadedAtBottom()) ).arg(showAtMsgId.bare)); - delayedShowAt(showAtMsgId); + delayedShowAt(showAtMsgId, highlightPart); } else if (_showAtMsgId != showAtMsgId) { clearAllLoadRequests(); - setMsgId(showAtMsgId); + setMsgId(showAtMsgId, highlightPart); firstLoadMessages(); doneShow(); } @@ -2049,7 +2051,7 @@ void HistoryWidget::showHistory( _cornerButtons.skipReplyReturn(skipId); } - setMsgId(showAtMsgId); + setMsgId(showAtMsgId, highlightPart); if (_historyInited) { DEBUG_LOG(("JumpToEnd(%1, %2, %3): " "Showing instant at %4." @@ -2152,6 +2154,7 @@ void HistoryWidget::showHistory( clearInlineBot(); _showAtMsgId = showAtMsgId; + _showAtMsgHighlightPart = highlightPart; _historyInited = false; _contactStatus = nullptr; @@ -3306,7 +3309,7 @@ void HistoryWidget::messagesReceived( } _delayedShowAtRequest = 0; - setMsgId(_delayedShowAtMsgId); + setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgHighlightPart); historyLoaded(); } if (session().supportMode()) { @@ -3528,9 +3531,16 @@ void HistoryWidget::loadMessagesDown() { }); } -void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { - if (!_history - || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) { +void HistoryWidget::delayedShowAt( + MsgId showAtMsgId, + const TextWithEntities &highlightPart) { + if (!_history) { + return; + } + if (_delayedShowAtMsgHighlightPart != highlightPart) { + _delayedShowAtMsgHighlightPart = highlightPart; + } + if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) { return; } @@ -3974,7 +3984,12 @@ void HistoryWidget::send(Api::SendOptions options) { ignoreSlowmodeCountdown)) { return; } + + // Just a flag not to drop reply info if we're not sending anything. + _justMarkingAsRead = !HasSendText(_field) + && message.webPage.url.isEmpty(); session().api().sendMessage(std::move(message)); + _justMarkingAsRead = false; clearFieldText(); if (_preview) { @@ -4123,7 +4138,12 @@ PeerData *HistoryWidget::peer() const { } // Sometimes _showAtMsgId is set directly. -void HistoryWidget::setMsgId(MsgId showAtMsgId) { +void HistoryWidget::setMsgId( + MsgId showAtMsgId, + const TextWithEntities &highlightPart) { + if (_showAtMsgHighlightPart != highlightPart) { + _showAtMsgHighlightPart = highlightPart; + } if (_showAtMsgId != showAtMsgId) { _showAtMsgId = showAtMsgId; if (_history) { @@ -4244,11 +4264,11 @@ void HistoryWidget::cornerButtonsShowAtPosition( ).arg(_history->peer->name() ).arg(_history->inboxReadTillId().bare ).arg(Logs::b(_history->loadedAtBottom()))); - showHistory(_peer->id, ShowAtUnreadMsgId); + showHistory(_peer->id, ShowAtUnreadMsgId, {}); } else if (_peer && position.fullId.peer == _peer->id) { - showHistory(_peer->id, position.fullId.msg); + showHistory(_peer->id, position.fullId.msg, {}); } else if (_migrated && position.fullId.peer == _migrated->peer->id) { - showHistory(_peer->id, -position.fullId.msg); + showHistory(_peer->id, -position.fullId.msg, {}); } } @@ -5197,7 +5217,7 @@ void HistoryWidget::updateFieldPlaceholder() { if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) { _field->setPlaceholder( rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)), - _inlineBot->username().size() + 2); + _inlineBotUsername.size() + 2); return; } @@ -5699,14 +5719,16 @@ int HistoryWidget::countInitialScrollTop() { const auto item = getItemFromHistoryOrMigrated(_showAtMsgId); const auto itemTop = _list->itemTop(item); if (itemTop < 0) { - setMsgId(0); + setMsgId(ShowAtUnreadMsgId); controller()->showToast(tr::lng_message_not_found(tr::now)); return countInitialScrollTop(); } else { const auto view = item->mainView(); Assert(view != nullptr); - enqueueMessageHighlight(view); + enqueueMessageHighlight( + view, + base::take(_showAtMsgHighlightPart)); const auto result = itemTopForHighlight(view); createUnreadBarIfBelowVisibleArea(result); return result; @@ -6278,6 +6300,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } else { _forwardPanel->editOptions(controller()->uiShow()); } + } else if (_replyTo && (e->modifiers() & Qt::ControlModifier)) { + jumpToReply(_replyTo); } else if (_replyTo) { editDraftOptions(); } else if (_kbReplyTo) { @@ -6306,12 +6330,9 @@ void HistoryWidget::editDraftOptions() { _preview->apply(webpage); }; const auto replyToId = reply.messageId; - const auto highlight = [=] { - controller()->showPeerHistory( - replyToId.peer, - Window::SectionShow::Way::Forward, - replyToId.msg); - }; + const auto highlight = crl::guard(this, [=](FullReplyTo to) { + jumpToReply(to); + }); using namespace HistoryView::Controls; EditDraftOptions({ @@ -6323,10 +6344,16 @@ void HistoryWidget::editDraftOptions() { .resolver = _preview->resolver(), .done = done, .highlight = highlight, - .clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); }, + .clearOldDraft = [=] { ClearDraftReplyTo(history, 0, replyToId); }, }); } +void HistoryWidget::jumpToReply(FullReplyTo to) { + if (const auto item = session().data().message(to.messageId)) { + JumpToMessageClickHandler(item, {}, to.quote)->onClick({}); + } +} + void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (!_history) return; @@ -6398,7 +6425,8 @@ void HistoryWidget::handlePeerMigration() { if (_peer != channel) { showHistory( channel->id, - (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId); + (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId, + {}); channel->session().api().chatParticipants().requestCountDelayed( channel); } else { @@ -6494,7 +6522,7 @@ bool HistoryWidget::showSlowmodeError() { if (const auto item = _history->latestSendingMessage()) { if (const auto view = item->mainView()) { animatedScrollToItem(item->id); - enqueueMessageHighlight(view); + enqueueMessageHighlight(view, {}); } return tr::lng_slowmode_no_many(tr::now); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index a78875e22..87b7edde9 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -75,6 +75,7 @@ class SpoilerAnimation; enum class ReportReason; class ChooseThemeController; class ContinuousScroll; +struct ChatPaintHighlight; } // namespace Ui namespace Window { @@ -146,7 +147,9 @@ public: void loadMessages(); void loadMessagesDown(); void firstLoadMessages(); - void delayedShowAt(MsgId showAtMsgId); + void delayedShowAt( + MsgId showAtMsgId, + const TextWithEntities &highlightPart); bool updateReplaceMediaButton(); void updateFieldPlaceholder(); @@ -160,7 +163,9 @@ public: History *history() const; PeerData *peer() const; - void setMsgId(MsgId showAtMsgId); + void setMsgId( + MsgId showAtMsgId, + const TextWithEntities &highlightPart = {}); MsgId msgId() const; bool hasTopBarShadow() const { @@ -177,8 +182,10 @@ public: bool touchScroll(const QPoint &delta); - void enqueueMessageHighlight(not_null view); - [[nodiscard]] float64 highlightOpacity( + void enqueueMessageHighlight( + not_null view, + const TextWithEntities &part); + [[nodiscard]] Ui::ChatPaintHighlight itemHighlight( not_null item) const; MessageIdsList getSelectedItems() const; @@ -218,7 +225,10 @@ public: void fastShowAtEnd(not_null history); bool applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); - void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); + void showHistory( + const PeerId &peer, + MsgId showAtMsgId, + const TextWithEntities &highlightPart); void setChooseReportMessagesDetails( Ui::ReportReason reason, Fn callback); @@ -540,6 +550,7 @@ private: void setupPreview(); void editDraftOptions(); + void jumpToReply(FullReplyTo to); void messagesReceived(not_null peer, const MTPmessages_Messages &messages, int requestId); void messagesFailed(const MTP::Error &error, int requestId); @@ -684,12 +695,14 @@ private: bool _canSendMessages = false; bool _canSendTexts = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; + TextWithEntities _showAtMsgHighlightPart; int _firstLoadRequest = 0; // Not real mtpRequestId. int _preloadRequest = 0; // Not real mtpRequestId. int _preloadDownRequest = 0; // Not real mtpRequestId. MsgId _delayedShowAtMsgId = -1; + TextWithEntities _delayedShowAtMsgHighlightPart; int _delayedShowAtRequest = 0; // Not real mtpRequestId. History *_supportPreloadHistory = nullptr; @@ -800,6 +813,7 @@ private: int _itemsRevealHeight = 0; bool _sponsoredMessagesStateKnown = false; + bool _justMarkingAsRead = false; object_ptr _topShadow; bool _inGrab = false; 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 09eb7e76c..21ce63e1a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -112,6 +112,7 @@ public: std::shared_ptr show); void setHistory(const SetHistoryArgs &args); + void updateTopicRootId(MsgId topicRootId); void init(); void editMessage(FullMsgId id, bool photoEditAllowed = false); @@ -129,7 +130,7 @@ public: [[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] FullMsgId editMsgId() const; [[nodiscard]] rpl::producer editMsgIdValue() const; - [[nodiscard]] rpl::producer scrollToItemRequests() const; + [[nodiscard]] rpl::producer jumpToItemRequests() const; [[nodiscard]] rpl::producer<> editPhotoRequests() const; [[nodiscard]] rpl::producer<> editOptionsRequests() const; [[nodiscard]] MessageToEdit queryToEdit(); @@ -205,7 +206,7 @@ private: QRect _shownMessagePreviewRect; rpl::event_stream _visibleChanged; - rpl::event_stream _scrollToItemRequests; + rpl::event_stream _jumpToItemRequests; rpl::event_stream<> _editOptionsRequests; rpl::event_stream<> _editPhotoRequests; @@ -229,6 +230,10 @@ void FieldHeader::setHistory(const SetHistoryArgs &args) { _topicRootId = args.topicRootId; } +void FieldHeader::updateTopicRootId(MsgId topicRootId) { + _topicRootId = topicRootId; +} + void FieldHeader::init() { sizeValue( ) | rpl::start_with_next([=](QSize size) { @@ -367,9 +372,14 @@ void FieldHeader::init() { if (_preview.parsed) { _editOptionsRequests.fire({}); } else if (isEditingMessage()) { - _scrollToItemRequests.fire(_editMsgId.current()); + _jumpToItemRequests.fire(FullReplyTo{ + .messageId = _editMsgId.current() + }); } else if (readyToForward()) { _forwardPanel->editOptions(_show); + } else if (reply + && (e->modifiers() & Qt::ControlModifier)) { + _jumpToItemRequests.fire_copy(reply); } else if (reply) { _editOptionsRequests.fire({}); } @@ -724,8 +734,8 @@ rpl::producer FieldHeader::editMsgIdValue() const { return _editMsgId.value(); } -rpl::producer FieldHeader::scrollToItemRequests() const { - return _scrollToItemRequests.events(); +rpl::producer FieldHeader::jumpToItemRequests() const { + return _jumpToItemRequests.events(); } rpl::producer<> FieldHeader::editPhotoRequests() const { @@ -855,6 +865,11 @@ Main::Session &ComposeControls::session() const { return _show->session(); } +void ComposeControls::updateTopicRootId(MsgId topicRootId) { + _topicRootId = topicRootId; + _header->updateTopicRootId(_topicRootId); +} + void ComposeControls::setHistory(SetHistoryArgs &&args) { _showSlowmodeError = std::move(args.showSlowmodeError); _sendActionFactory = std::move(args.sendActionFactory); @@ -1332,6 +1347,7 @@ void ComposeControls::init() { _header->editOptionsRequests( ) | rpl::start_with_next([=] { const auto history = _history; + const auto topicRootId = _topicRootId; const auto reply = _header->replyingToMessage(); const auto webpage = _preview->draft(); @@ -1344,10 +1360,11 @@ void ComposeControls::init() { cancelReplyMessage(); } _preview->apply(webpage); + _field->setFocus(); }; const auto replyToId = reply.messageId; - const auto highlight = crl::guard(_wrap.get(), [=] { - _scrollToItemRequests.fire_copy(replyToId); + const auto highlight = crl::guard(_wrap.get(), [=](FullReplyTo to) { + _jumpToItemRequests.fire_copy(to); }); using namespace HistoryView::Controls; @@ -1360,7 +1377,10 @@ void ComposeControls::init() { .resolver = _preview->resolver(), .done = done, .highlight = highlight, - .clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); }, + .clearOldDraft = [=] { ClearDraftReplyTo( + history, + topicRootId, + replyToId); }, }); }, _wrap->lifetime()); @@ -1920,6 +1940,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _header->replyToMessage({}); if (_preview) { _preview->apply({ .removed = true }); + _preview->setDisabled(false); } _canReplaceMedia = false; _photoEditMedia = nullptr; @@ -1958,6 +1979,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _preview->apply( Data::WebPageDraft::FromItem(item), false); + _preview->setDisabled(media && !media->webpage()); } return true; } @@ -1989,6 +2011,9 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { cancelForward(); } _header->editMessage({}); + if (_preview) { + _preview->setDisabled(false); + } } } @@ -2873,16 +2898,10 @@ Data::WebPageDraft ComposeControls::webPageDraft() const { return _preview ? _preview->draft() : Data::WebPageDraft(); } -rpl::producer ComposeControls::scrollRequests() const { +rpl::producer ComposeControls::jumpToItemRequests() const { return rpl::merge( - _header->scrollToItemRequests(), - _scrollToItemRequests.events() - ) | rpl::map([=](FullMsgId id) -> Data::MessagePosition { - if (const auto item = session().data().message(id)) { - return item->position(); - } - return {}; - }); + _header->jumpToItemRequests(), + _jumpToItemRequests.events()); } bool ComposeControls::isEditingMessage() const { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 18a76132e..9ae515d6c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -135,6 +135,7 @@ public: [[nodiscard]] Main::Session &session() const; void setHistory(SetHistoryArgs &&args); + void updateTopicRootId(MsgId topicRootId); void setCurrentDialogsEntryState(Dialogs::EntryState state); [[nodiscard]] PeerData *sendAsPeer() const; @@ -158,7 +159,7 @@ public: [[nodiscard]] rpl::producer> attachRequests() const; [[nodiscard]] rpl::producer fileChosen() const; [[nodiscard]] rpl::producer photoChosen() const; - [[nodiscard]] rpl::producer scrollRequests() const; + [[nodiscard]] rpl::producer jumpToItemRequests() const; [[nodiscard]] rpl::producer inlineResultChosen() const; [[nodiscard]] rpl::producer sendActionUpdates() const; [[nodiscard]] rpl::producer> viewportEvents() const; @@ -357,7 +358,7 @@ private: const std::unique_ptr _wrap; const std::unique_ptr _writeRestricted; - rpl::event_stream _scrollToItemRequests; + rpl::event_stream _jumpToItemRequests; std::optional _backgroundRect; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 483a8d280..65a1cd6de 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -99,9 +99,8 @@ public: not_null history); ~PreviewWrap(); - [[nodiscard]] rpl::producer showQuoteSelector( - not_null item, - const TextWithEntities "e); + [[nodiscard]] rpl::producer showQuoteSelector( + const SelectedQuote "e); [[nodiscard]] rpl::producer showLinkSelector( const TextWithTags &message, Data::WebPageDraft webpage, @@ -212,12 +211,14 @@ PreviewWrap::~PreviewWrap() { } } -rpl::producer PreviewWrap::showQuoteSelector( - not_null item, - const TextWithEntities "e) { +rpl::producer PreviewWrap::showQuoteSelector( + const SelectedQuote "e) { _selection.reset(TextSelection()); - _element = item->createView(_delegate.get()); + const auto item = quote.item; + const auto group = item->history()->owner().groups().find(item); + const auto leader = group ? group->items.front().get() : item; + _element = leader->createView(_delegate.get()); _link = _pressedLink = nullptr; if (const auto was = base::take(_draftItem)) { @@ -233,10 +234,13 @@ rpl::producer PreviewWrap::showQuoteSelector( initElement(); - _selection = _element->selectionFromQuote(quote); + _selection = _element->selectionFromQuote(item, quote.text); return _selection.value( ) | rpl::map([=](TextSelection selection) { - return _element->selectedQuote(selection); + if (const auto result = _element->selectedQuote(selection)) { + return result; + } + return SelectedQuote{ item }; }); } @@ -584,7 +588,7 @@ void DraftOptionsBox( struct State { rpl::variable
shown; rpl::lifetime shownLifetime; - rpl::variable quote; + rpl::variable quote; Data::WebPageDraft webpage; WebPageData *preview = nullptr; QString link; @@ -596,7 +600,7 @@ void DraftOptionsBox( rpl::lifetime resolveLifetime; }; const auto state = box->lifetime().make_state(); - state->quote = draft.reply.quote; + state->quote = SelectedQuote{ replyItem, draft.reply.quote }; state->webpage = draft.webpage; state->preview = previewData; state->shown = previewData ? Section::Link : Section::Reply; @@ -635,7 +639,10 @@ void DraftOptionsBox( const auto &clearOldDraft = args.clearOldDraft; const auto resolveReply = [=] { auto result = draft.reply; - result.quote = state->quote.current(); + if (const auto current = state->quote.current()) { + result.messageId = current.item->fullId(); + result.quote = current.text; + } return result; }; const auto finish = [=]( @@ -650,21 +657,26 @@ void DraftOptionsBox( const auto setupReplyActions = [=] { AddFilledSkip(bottom); - Settings::AddButton( - bottom, - tr::lng_reply_in_another_chat(), - st::settingsButton, - { &st::menuIconReplace } - )->setClickedCallback([=] { - ShowReplyToChatBox(show, resolveReply(), clearOldDraft); - }); + const auto item = state->quote.current().item; + if (item->allowsForward()) { + Settings::AddButton( + bottom, + tr::lng_reply_in_another_chat(), + st::settingsButton, + { &st::menuIconReplace } + )->setClickedCallback([=] { + ShowReplyToChatBox(show, resolveReply(), clearOldDraft); + }); + } Settings::AddButton( bottom, tr::lng_reply_show_in_chat(), st::settingsButton, { &st::menuIconShowInChat } - )->setClickedCallback(highlight); + )->setClickedCallback([=] { + highlight(resolveReply()); + }); Settings::AddButton( bottom, @@ -675,7 +687,7 @@ void DraftOptionsBox( finish({}, state->webpage); }); - if (!replyItem->originalText().empty()) { + if (!item->originalText().empty()) { AddFilledSkip(bottom); Settings::AddDividerText( bottom, @@ -804,7 +816,6 @@ void DraftOptionsBox( state->shownLifetime.destroy(); if (shown == Section::Reply) { state->quote = state->wrap->showQuoteSelector( - replyItem, state->quote.current()); setupReplyActions(); } else { @@ -823,8 +834,8 @@ void DraftOptionsBox( auto save = rpl::combine( state->quote.value(), state->shown.value() - ) | rpl::map([=](const TextWithEntities "e, Section shown) { - return (quote.empty() || shown != Section::Reply) + ) | rpl::map([=](const SelectedQuote "e, Section shown) { + return (quote.text.empty() || shown != Section::Reply) ? tr::lng_settings_save() : tr::lng_reply_quote_selected(); }) | rpl::flatten_latest(); @@ -839,14 +850,20 @@ void DraftOptionsBox( if (replyItem) { args.show->session().data().itemRemoved( ) | rpl::filter([=](not_null removed) { - return removed == replyItem; + const auto current = state->quote.current().item; + if ((removed == replyItem) || (removed == current)) { + return true; + } + const auto group = current->history()->owner().groups().find( + current); + return (group && ranges::contains(group->items, removed)); }) | rpl::start_with_next([=] { if (previewData) { state->tabs = nullptr; box->setPinnedToTopContent( object_ptr(nullptr)); box->setNoContentMargin(false); - box->setTitle(state->quote.current().empty() + box->setTitle(state->quote.current().text.empty() ? tr::lng_reply_options_header() : tr::lng_reply_options_quote()); state->shown = Section::Link; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h index 798e1fa0c..abe19b39e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h @@ -32,7 +32,7 @@ struct EditDraftOptionsArgs { std::vector links; std::shared_ptr resolver; Fn done; - Fn highlight; + Fn highlight; Fn clearOldDraft; }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 30869de20..98799149f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -400,13 +400,6 @@ void ForwardPanel::paint( }); } -void ClearDraftReplyTo(not_null thread, FullMsgId equalTo) { - ClearDraftReplyTo( - thread->owningHistory(), - thread->topicRootId(), - equalTo); -} - void ClearDraftReplyTo( not_null history, MsgId topicRootId, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h index 7e3d2fac4..59c079d6f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h @@ -72,7 +72,6 @@ private: }; -void ClearDraftReplyTo(not_null thread, FullMsgId equalTo); void ClearDraftReplyTo( not_null history, MsgId topicRootId, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 9b88150c5..63b1edafe 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -590,7 +590,7 @@ bool AddReplyToMessageAction( const ContextMenuRequest &request, not_null list) { const auto context = list->elementContext(); - const auto item = request.item; + const auto item = request.quoteItem ? request.quoteItem : request.item; const auto topic = item ? item->topic() : nullptr; const auto peer = item ? item->history()->peer.get() : nullptr; if (!item @@ -601,15 +601,7 @@ bool AddReplyToMessageAction( const auto canSendReply = topic ? Data::CanSendAnything(topic) : Data::CanSendAnything(peer); - const auto canReply = canSendReply || [&] { - const auto peer = item->history()->peer; - if (const auto chat = peer->asChat()) { - return !chat->isForbidden(); - } else if (const auto channel = peer->asChannel()) { - return !channel->isForbidden(); - } - return true; - }(); + const auto canReply = canSendReply || item->allowsForward(); if (!canReply) { return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 64040ae41..fb99d2cc6 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -48,6 +48,7 @@ struct ContextMenuRequest { SelectedItems selectedItems; TextForMimeData selectedText; TextWithEntities quote; + HistoryItem *quoteItem = nullptr; bool overSelection = false; PointState pointState = PointState(); }; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index e7d9f1385..88251cf7a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/; return session->tryResolveWindow(); } +[[nodiscard]] bool CheckQuoteEntities( + const EntitiesInText "eEntities, + const TextWithEntities &original, + TextSelection selection) { + auto left = quoteEntities; + const auto allowed = std::array{ + EntityType::Bold, + EntityType::Italic, + EntityType::Underline, + EntityType::StrikeOut, + EntityType::Spoiler, + EntityType::CustomEmoji, + }; + for (const auto &entity : original.entities) { + const auto from = entity.offset(); + const auto till = from + entity.length(); + if (till <= selection.from || from >= selection.to) { + continue; + } + const auto quoteFrom = std::max(from, int(selection.from)); + const auto quoteTill = std::min(till, int(selection.to)); + const auto cut = EntityInText( + entity.type(), + quoteFrom - int(selection.from), + quoteTill - quoteFrom, + entity.data()); + const auto i = ranges::find(left, cut); + if (i != left.end()) { + left.erase(i); + } else if (ranges::contains(allowed, cut.type())) { + return false; + } + } + return left.empty(); +}; + } // namespace std::unique_ptr MakePathShiftGradient( @@ -111,11 +147,6 @@ bool DefaultElementDelegate::elementUnderCursor( return false; } -float64 DefaultElementDelegate::elementHighlightOpacity( - not_null item) const { - return 0.; -} - bool DefaultElementDelegate::elementInSelectionMode() { return false; } @@ -593,6 +624,9 @@ void Element::paintHighlight( Painter &p, const PaintContext &context, int geometryHeight) const { + if (context.highlight.opacity == 0.) { + return; + } const auto top = marginTop(); const auto bottom = marginBottom(); const auto fill = qMin(top, bottom); @@ -608,18 +642,9 @@ void Element::paintCustomHighlight( int y, int height, not_null item) const { - const auto opacity = delegate()->elementHighlightOpacity(item); - if (opacity == 0.) { - return; - } const auto o = p.opacity(); - p.setOpacity(o * opacity); - p.fillRect( - 0, - y, - width(), - height, - context.st->msgSelectOverlay()); + p.setOpacity(o * context.highlight.opacity); + p.fillRect(0, y, width(), height, context.st->msgSelectOverlay()); p.setOpacity(o); } @@ -1402,7 +1427,12 @@ HistoryMessageReply *Element::displayedReply() const { } bool Element::toggleSelectionByHandlerClick( - const ClickHandlerPtr &handler) const { + const ClickHandlerPtr &handler) const { + return false; +} + +bool Element::allowTextSelectionByHandler( + const ClickHandlerPtr &handler) const { return false; } @@ -1572,6 +1602,105 @@ TextSelection Element::adjustSelection( return selection; } +SelectedQuote Element::FindSelectedQuote( + const Ui::Text::String &text, + TextSelection selection, + not_null item) { + if (selection.to > text.length()) { + return {}; + } + auto modified = selection; + for (const auto &modification : text.modifications()) { + if (modification.position >= selection.to) { + break; + } else if (modification.position <= selection.from) { + modified.from += modification.skipped; + if (modification.added + && modification.position < selection.from) { + --modified.from; + } + } + modified.to += modification.skipped; + if (modification.added && modified.to > modified.from) { + --modified.to; + } + } + auto result = item->originalText(); + if (modified.empty() || modified.to > result.text.size()) { + return {}; + } + result.text = result.text.mid( + modified.from, + modified.to - modified.from); + const auto allowed = std::array{ + EntityType::Bold, + EntityType::Italic, + EntityType::Underline, + EntityType::StrikeOut, + EntityType::Spoiler, + EntityType::CustomEmoji, + }; + for (auto i = result.entities.begin(); i != result.entities.end();) { + const auto offset = i->offset(); + const auto till = offset + i->length(); + if ((till <= modified.from) + || (offset >= modified.to) + || !ranges::contains(allowed, i->type())) { + i = result.entities.erase(i); + } else { + if (till > modified.to) { + i->shrinkFromRight(till - modified.to); + } + i->shiftLeft(modified.from); + ++i; + } + } + return { item, result }; +} + +TextSelection Element::FindSelectionFromQuote( + const Ui::Text::String &text, + not_null item, + const TextWithEntities "e) { + if (quote.empty()) { + return {}; + } + const auto &original = item->originalText(); + auto result = TextSelection(); + auto offset = 0; + while (true) { + const auto i = original.text.indexOf(quote.text, offset); + if (i < 0) { + return {}; + } + auto selection = TextSelection{ + uint16(i), + uint16(i + quote.text.size()), + }; + if (CheckQuoteEntities(quote.entities, original, selection)) { + result = selection; + break; + } + offset = i + 1; + } + //for (const auto &modification : text.modifications()) { + // if (modification.position >= selection.to) { + // break; + // } else if (modification.position <= selection.from) { + // modified.from += modification.skipped; + // if (modification.added + // && modification.position < selection.from) { + // --modified.from; + // } + // } + // modified.to += modification.skipped; + // if (modification.added && modified.to > modified.from) { + // --modified.to; + // } + //} + return result; +} + Reactions::ButtonParameters Element::reactionButtonParameters( QPoint position, const TextState &reactionState) const { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index b51487703..db3933b98 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -69,8 +69,6 @@ class ElementDelegate { public: virtual Context elementContext() = 0; virtual bool elementUnderCursor(not_null view) = 0; - [[nodiscard]] virtual float64 elementHighlightOpacity( - not_null item) const = 0; virtual bool elementInSelectionMode() = 0; virtual bool elementIntersectsRange( not_null view, @@ -120,8 +118,6 @@ public: class DefaultElementDelegate : public ElementDelegate { public: bool elementUnderCursor(not_null view) override; - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, @@ -270,6 +266,16 @@ struct TopicButton { int nameVersion = 0; }; +struct SelectedQuote { + HistoryItem *item = nullptr; + TextWithEntities text; + + explicit operator bool() const { + return item && !text.empty(); + } + friend inline bool operator==(SelectedQuote, SelectedQuote) = default; +}; + class Element : public Object , public RuntimeComposer @@ -391,19 +397,24 @@ public: QPoint point, InfoDisplayType type) const; virtual TextForMimeData selectedText(TextSelection selection) const = 0; - virtual TextWithEntities selectedQuote(TextSelection selection) const = 0; - virtual TextWithEntities selectedQuote( - const Ui::Text::String &text, + virtual SelectedQuote selectedQuote( TextSelection selection) const = 0; virtual TextSelection selectionFromQuote( - const TextWithEntities "e) const = 0; - virtual TextSelection selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const = 0; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, TextSelectType type) const; + [[nodiscard]] static SelectedQuote FindSelectedQuote( + const Ui::Text::String &text, + TextSelection selection, + not_null item); + [[nodiscard]] static TextSelection FindSelectionFromQuote( + const Ui::Text::String &text, + not_null item, + const TextWithEntities "e); + [[nodiscard]] virtual auto reactionButtonParameters( QPoint position, const TextState &reactionState) const -> Reactions::ButtonParameters; @@ -451,6 +462,8 @@ public: } [[nodiscard]] virtual bool toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const; + [[nodiscard]] virtual bool allowTextSelectionByHandler( + const ClickHandlerPtr &handler) const; struct VerticalRepaintRange { int top = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 2938fffdd..410b1a337 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -707,8 +707,12 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const { return _items.front()->data()->position() > position; } -void ListWidget::highlightMessage(FullMsgId itemId) { - _highlighter.highlight(itemId); +void ListWidget::highlightMessage( + FullMsgId itemId, + const TextWithEntities &part) { + if (const auto view = viewForItem(itemId)) { + _highlighter.highlight(view, part); + } } void ListWidget::showAroundPosition( @@ -741,12 +745,12 @@ bool ListWidget::jumpToBottomInsteadOfUnread() const { void ListWidget::showAtPosition( Data::MessagePosition position, - anim::type animated, + const Window::SectionShow ¶ms, Fn done) { const auto showAtUnread = (position == Data::UnreadMessagePosition); if (showAtUnread && jumpToBottomInsteadOfUnread()) { - showAtPosition(Data::MaxMessagePosition, animated, std::move(done)); + showAtPosition(Data::MaxMessagePosition, params, std::move(done)); return; } @@ -766,24 +770,24 @@ void ListWidget::showAtPosition( _bar = {}; } checkUnreadBarCreation(); - return showAtPositionNow(position, animated, done); + return showAtPositionNow(position, params, done); }); - } else if (!showAtPositionNow(position, animated, done)) { + } else if (!showAtPositionNow(position, params, done)) { showAroundPosition(position, [=] { - return showAtPositionNow(position, animated, done); + return showAtPositionNow(position, params, done); }); } } bool ListWidget::showAtPositionNow( Data::MessagePosition position, - anim::type animated, + const Window::SectionShow ¶ms, Fn done) { if (const auto scrollTop = scrollTopForPosition(position)) { - computeScrollTo(*scrollTop, position, animated); + computeScrollTo(*scrollTop, position, params.animated); if (position != Data::MaxMessagePosition && position != Data::UnreadMessagePosition) { - highlightMessage(position.fullId); + highlightMessage(position.fullId, params.highlightPart); } if (done) { const auto found = !position.fullId.peer @@ -1655,11 +1659,6 @@ bool ListWidget::elementUnderCursor( return (_overElement == view); } -float64 ListWidget::elementHighlightOpacity( - not_null item) const { - return _highlighter.progress(item); -} - bool ListWidget::elementInSelectionMode() { return inSelectionMode(); } @@ -2088,6 +2087,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { }); auto context = preparePaintContext(clip); + context.highlightPathCache = &_highlightPathCache; if (from == end(_items)) { _delegate->listPaintEmpty(p, context); return; @@ -2108,6 +2108,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { = _reactionsManager->currentReactionPaintInfo(); context.outbg = view->hasOutLayout(); context.selection = itemRenderSelection(view); + context.highlight = _highlighter.state(item); view->draw(p, context); } if (_translateTracker) { @@ -2142,7 +2143,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { } else if (item->isUnreadMention() && !item->isUnreadMedia()) { readContents.insert(item); - _highlighter.enqueue(view); + _highlighter.enqueue(view, {}); } } session->data().reactions().poll(item, context.now); @@ -2576,7 +2577,6 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : _overElement ? _overElement->data().get() : nullptr; - const auto overItemView = viewForItem(overItem); const auto clickedReaction = link ? link->property( kReactionsCountEmojiProperty).value() @@ -2603,9 +2603,12 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { request.view = _overElement; request.item = overItem; request.pointState = _overState.pointState; - request.quote = (overItemView && _selectedTextItem == overItem) - ? overItemView->selectedQuote(_selectedTextRange) - : TextWithEntities(); + const auto quote = (_overElement + && _selectedTextItem == _overElement->data()) + ? _overElement->selectedQuote(_selectedTextRange) + : SelectedQuote(); + request.quote = quote.text; + request.quoteItem = quote.item; request.selectedText = _selectedText; request.selectedItems = collectSelectedItems(); const auto hasSelection = !request.selectedItems.empty() diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 138f39cca..95b641a04 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -48,6 +48,10 @@ struct ChosenReaction; struct ButtonParameters; } // namespace HistoryView::Reactions +namespace Window { +struct SectionShow; +} // namespace Window + namespace HistoryView { struct TextState; @@ -227,11 +231,13 @@ public: [[nodiscard]] bool animatedScrolling() const; bool isAbovePosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const; - void highlightMessage(FullMsgId itemId); + void highlightMessage( + FullMsgId itemId, + const TextWithEntities &part); void showAtPosition( Data::MessagePosition position, - anim::type animated = anim::type::normal, + const Window::SectionShow ¶ms, Fn done = nullptr); void refreshViewer(); @@ -292,8 +298,6 @@ public: // ElementDelegate interface. Context elementContext() override; bool elementUnderCursor(not_null view) override; - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, @@ -430,7 +434,7 @@ private: Fn overrideInitialScroll); bool showAtPositionNow( Data::MessagePosition position, - anim::type animated, + const Window::SectionShow ¶ms, Fn done); Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; @@ -645,6 +649,7 @@ private: base::flat_map _hiddenSenderUserpics; const std::unique_ptr _pathGradient; + QPainterPath _highlightPathCache; base::unique_qptr _emptyInfo = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a5eebddfd..f5a6d655d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; return std::nullopt; } -[[nodiscard]] bool CheckQuoteEntities( - const EntitiesInText "eEntities, - const TextWithEntities &original, - TextSelection selection) { - auto left = quoteEntities; - const auto allowed = std::array{ - EntityType::Bold, - EntityType::Italic, - EntityType::Underline, - EntityType::StrikeOut, - EntityType::Spoiler, - EntityType::CustomEmoji, - }; - for (const auto &entity : original.entities) { - const auto from = entity.offset(); - const auto till = from + entity.length(); - if (till <= selection.from || from >= selection.to) { - continue; - } - const auto quoteFrom = std::max(from, int(selection.from)); - const auto quoteTill = std::min(till, int(selection.to)); - const auto cut = EntityInText( - entity.type(), - quoteFrom - int(selection.from), - quoteTill - quoteFrom, - entity.data()); - const auto i = ranges::find(left, cut); - if (i != left.end()) { - left.erase(i); - } else if (ranges::contains(allowed, cut.type())) { - return false; - } - } - return left.empty(); -}; - class KeyboardStyle : public ReplyKeyboard::Style { public: KeyboardStyle(const style::BotKeyboardButton &st); @@ -1018,6 +982,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.translate(-reactionsPosition); } + if (context.highlightPathCache) { + context.highlightInterpolateTo = g; + context.highlightPathCache->clear(); + } if (bubble) { if (displayFromName() && item->displayFrom() @@ -1110,6 +1078,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { - (_bottomInfo.height() - st::msgDateFont->height)); } auto textSelection = context.selection; + auto highlightRange = context.highlight.range; const auto mediaHeight = mediaDisplayed ? media->height() : 0; const auto paintMedia = [&](int top) { if (!mediaDisplayed) { @@ -1118,6 +1087,8 @@ void Message::draw(Painter &p, const PaintContext &context) const { const auto mediaSelection = _invertMedia ? context.selection : skipTextSelection(context.selection); + const auto maybeMediaHighlight = context.highlightPathCache + && context.highlightPathCache->isEmpty(); auto mediaPosition = QPoint(inner.left(), top); p.translate(mediaPosition); media->draw(p, context.translated( @@ -1130,6 +1101,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { context.reactionInfo->effectOffset -= add; } } + if (maybeMediaHighlight + && !context.highlightPathCache->isEmpty()) { + context.highlightPathCache->translate(mediaPosition); + } p.translate(-mediaPosition); }; if (mediaDisplayed && _invertMedia) { @@ -1141,8 +1116,12 @@ void Message::draw(Painter &p, const PaintContext &context) const { + mediaHeight + (mediaOnBottom ? 0 : st::mediaInBubbleSkip)); textSelection = media->skipSelection(textSelection); + highlightRange = media->skipSelection(highlightRange); } - paintText(p, trect, context.withSelection(textSelection)); + auto copy = context; + copy.selection = textSelection; + copy.highlight.range = highlightRange; + paintText(p, trect, copy); if (mediaDisplayed && !_invertMedia) { paintMedia(trect.y() + trect.height() - mediaHeight); if (context.reactionInfo && !displayInfo && !_reactions) { @@ -1224,6 +1203,20 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.restoreTextPalette(); + if (context.highlightPathCache + && !context.highlightPathCache->isEmpty()) { + const auto alpha = int(0.25 + * context.highlight.collapsion + * context.highlight.opacity + * 255); + if (alpha > 0) { + context.highlightPathCache->setFillRule(Qt::WindingFill); + auto color = context.messageStyle()->textPalette.linkFg->c; + color.setAlpha(alpha); + p.fillPath(*context.highlightPathCache, color); + } + } + if (roll) { p.restore(); } @@ -1651,6 +1644,7 @@ void Message::paintText( width()); trect.setY(trect.y() + botTop->height); } + auto highlightRequest = context.computeHighlightCache(); text().draw(p, { .position = trect.topLeft(), .availableWidth = trect.width(), @@ -1663,6 +1657,7 @@ void Message::paintText( .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } @@ -2651,7 +2646,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const { return result; } -TextWithEntities Message::selectedQuote(TextSelection selection) const { +SelectedQuote Message::selectedQuote(TextSelection selection) const { const auto item = data(); const auto &translated = item->translatedText(); const auto &original = item->originalText(); @@ -2666,7 +2661,7 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const { const auto textSelection = mediaBefore ? media->skipSelection(selection) : selection; - return selectedQuote(text(), textSelection); + return FindSelectedQuote(text(), textSelection, data()); } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { return media->selectedQuote(selection); @@ -2675,124 +2670,30 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const { return {}; } -TextWithEntities Message::selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const { - if (selection.to > text.length()) { - return {}; - } - auto modified = selection; - for (const auto &modification : text.modifications()) { - if (modification.position >= selection.to) { - break; - } else if (modification.position <= selection.from) { - modified.from += modification.skipped; - if (modification.added - && modification.position < selection.from) { - --modified.from; - } - } - modified.to += modification.skipped; - if (modification.added && modified.to > modified.from) { - --modified.to; - } - } - auto result = data()->originalText(); - if (modified.empty() || modified.to > result.text.size()) { - return {}; - } - result.text = result.text.mid( - modified.from, - modified.to - modified.from); - const auto allowed = std::array{ - EntityType::Bold, - EntityType::Italic, - EntityType::Underline, - EntityType::StrikeOut, - EntityType::Spoiler, - EntityType::CustomEmoji, - }; - for (auto i = result.entities.begin(); i != result.entities.end();) { - const auto offset = i->offset(); - const auto till = offset + i->length(); - if ((till <= modified.from) - || (offset >= modified.to) - || !ranges::contains(allowed, i->type())) { - i = result.entities.erase(i); - } else { - if (till > modified.to) { - i->shrinkFromRight(till - modified.to); - } - i->shiftLeft(modified.from); - ++i; - } - } - return result; -} - TextSelection Message::selectionFromQuote( + not_null item, const TextWithEntities "e) const { - const auto item = data(); + if (quote.empty()) { + return {}; + } const auto &translated = item->translatedText(); const auto &original = item->originalText(); - if (&translated != &original || quote.empty()) { + if (&translated != &original) { return {}; } else if (hasVisibleText()) { const auto media = this->media(); const auto mediaDisplayed = media && media->isDisplayed(); const auto mediaBefore = mediaDisplayed && invertMedia(); - const auto result = selectionFromQuote(text(), quote); + const auto result = FindSelectionFromQuote(text(), item, quote); return mediaBefore ? media->unskipSelection(result) : result; } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { - return media->selectionFromQuote(quote); + return media->selectionFromQuote(item, quote); } } return {}; } -TextSelection Message::selectionFromQuote( - const Ui::Text::String &text, - const TextWithEntities "e) const { - if (quote.empty()) { - return {}; - } - const auto &original = data()->originalText(); - auto result = TextSelection(); - auto offset = 0; - while (true) { - const auto i = original.text.indexOf(quote.text, offset); - if (i < 0) { - return {}; - } - auto selection = TextSelection{ - uint16(i), - uint16(i + quote.text.size()), - }; - if (CheckQuoteEntities(quote.entities, original, selection)) { - result = selection; - break; - } - offset = i + 1; - } - //for (const auto &modification : text.modifications()) { - // if (modification.position >= selection.to) { - // break; - // } else if (modification.position <= selection.from) { - // modified.from += modification.skipped; - // if (modification.added - // && modification.position < selection.from) { - // --modified.from; - // } - // } - // modified.to += modification.skipped; - // if (modification.added && modified.to > modified.from) { - // --modified.to; - // } - //} - return result; -} - TextSelection Message::adjustSelection( TextSelection selection, TextSelectType type) const { @@ -3247,6 +3148,16 @@ bool Message::toggleSelectionByHandlerClick( return false; } +bool Message::allowTextSelectionByHandler( + const ClickHandlerPtr &handler) const { + if (const auto media = this->media()) { + if (media->allowTextSelectionByHandler(handler)) { + return true; + } + } + return false; +} + bool Message::hasFromName() const { switch (context()) { case Context::AdminLog: @@ -4148,7 +4059,7 @@ void Message::refreshInfoSkipBlock() { return false; } else if (item->Has()) { return false; - } else if (media && media->isDisplayed()) { + } else if (media && media->isDisplayed() && !_invertMedia) { return false; } else if (_reactions) { return false; diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 7506bf355..aeba7c1aa 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -95,14 +95,9 @@ public: QPoint point, InfoDisplayType type) const override; TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; - TextWithEntities selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( - const TextWithEntities "e) const override; - TextSelection selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const override; TextSelection adjustSelection( TextSelection selection, @@ -145,6 +140,8 @@ public: [[nodiscard]] HistoryMessageReply *displayedReply() const override; [[nodiscard]] bool toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const override; + [[nodiscard]] bool allowTextSelectionByHandler( + const ClickHandlerPtr &handler) const override; [[nodiscard]] int infoWidth() const override; [[nodiscard]] int bottomInfoFirstLineWidth() const override; [[nodiscard]] bool bottomInfoIsWide() const override; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 0fbaf0349..0472e4eb1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -262,7 +262,7 @@ void PinnedWidget::showAtPosition( FullMsgId originId) { _inner->showAtPosition( position, - anim::type::normal, + {}, _cornerButtons.doneJumpFrom(position.fullId, originId)); } @@ -346,7 +346,7 @@ void PinnedWidget::restoreState(not_null memento) { ? FullMsgId(_history->peer->id, highlight) : FullMsgId(_migratedPeer->id, -highlight)), .date = TimeId(0), - }, anim::type::instant); + }, { Window::SectionShow::Way::Forward, anim::type::instant }); } } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1048b98b9..c652ab2e8 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -128,10 +128,12 @@ rpl::producer RootViewContent( RepliesMemento::RepliesMemento( not_null history, MsgId rootId, - MsgId highlightId) + MsgId highlightId, + const TextWithEntities &highlightPart) : _history(history) , _rootId(rootId) -, _highlightId(highlightId) { +, _highlightId(highlightId) +, _highlightPart(highlightPart) { if (highlightId) { _list.setAroundPosition({ .fullId = FullMsgId(_history->peer->id, highlightId), @@ -328,6 +330,7 @@ RepliesWidget::RepliesWidget( Controls::ShowReplyToChatBox(controller->uiShow(), { fullId }); } else { replyToMessage(fullId); + _composeControls->focus(); } }, _inner->lifetime()); @@ -490,6 +493,7 @@ void RepliesWidget::setupTopicViewer() { ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { if (_rootId == change.oldId) { _rootId = change.newId.msg; + _composeControls->updateTopicRootId(_rootId); _sendAction = owner->sendActionManager().repliesPainter( _history, _rootId); @@ -787,9 +791,11 @@ void RepliesWidget::setupComposeControls() { sendInlineResult(chosen.result, chosen.bot, chosen.options, localId); }, lifetime()); - _composeControls->scrollRequests( - ) | rpl::start_with_next([=](Data::MessagePosition pos) { - showAtPosition(pos); + _composeControls->jumpToItemRequests( + ) | rpl::start_with_next([=](FullReplyTo to) { + if (const auto item = session().data().message(to.messageId)) { + JumpToMessageClickHandler(item, {}, to.quote)->onClick({}); + } }, lifetime()); _composeControls->scrollKeyEvents( @@ -1859,16 +1865,22 @@ void RepliesWidget::finishSending() { refreshTopBarActiveChat(); } +void RepliesWidget::showAtPosition( + Data::MessagePosition position, + FullMsgId originItemId) { + showAtPosition(position, originItemId, {}); +} + void RepliesWidget::showAtPosition( Data::MessagePosition position, FullMsgId originItemId, - anim::type animated) { + const Window::SectionShow ¶ms) { _lastShownAt = position.fullId; controller()->setActiveChatEntry(activeChat()); const auto ignore = (position.fullId.msg == _rootId); _inner->showAtPosition( position, - animated, + params, _cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore)); } @@ -1960,7 +1972,7 @@ bool RepliesWidget::showInternal( if (logMemento->getHistory() == history() && logMemento->getRootId() == _rootId) { restoreState(logMemento); - if (!logMemento->getHighlightId()) { + if (!logMemento->highlightId()) { showAtPosition(Data::UnreadMessagePosition); } if (params.reapplyLocalDraft) { @@ -2008,7 +2020,7 @@ bool RepliesWidget::showMessage( } const auto id = FullMsgId(_history->peer->id, messageId); const auto message = _history->owner().message(id); - if (!message) { + if (!message || !message->inThread(_rootId)) { return false; } const auto originMessage = [&]() -> HistoryItem* { @@ -2024,13 +2036,13 @@ bool RepliesWidget::showMessage( } return nullptr; }(); - if (!originMessage) { - return false; - } - const auto originItemId = (_cornerButtons.replyReturn() != originMessage) + const auto currentReplyReturn = _cornerButtons.replyReturn(); + const auto originItemId = !originMessage + ? FullMsgId() + : (currentReplyReturn != originMessage) ? originMessage->fullId() : FullMsgId(); - showAtPosition(message->position(), originItemId); + showAtPosition(message->position(), originItemId, params); return true; } @@ -2132,11 +2144,15 @@ void RepliesWidget::restoreState(not_null memento) { } _cornerButtons.setReplyReturns(memento->replyReturns()); _inner->restoreState(memento->list()); - if (const auto highlight = memento->getHighlightId()) { + if (const auto highlight = memento->highlightId()) { + auto params = Window::SectionShow( + Window::SectionShow::Way::Forward, + anim::type::instant); + params.highlightPart = memento->highlightPart(); showAtPosition(Data::MessagePosition{ .fullId = FullMsgId(_history->peer->id, highlight), .date = TimeId(0), - }, {}, anim::type::instant); + }, {}, params); } } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 14104e913..e8d3e6139 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -208,8 +208,11 @@ private: void showAtEnd(); void showAtPosition( Data::MessagePosition position, - FullMsgId originItemId = {}, - anim::type animated = anim::type::normal); + FullMsgId originItemId = {}); + void showAtPosition( + Data::MessagePosition position, + FullMsgId originItemId, + const Window::SectionShow ¶ms); void finishSending(); void setupComposeControls(); @@ -377,7 +380,8 @@ public: RepliesMemento( not_null history, MsgId rootId, - MsgId highlightId = 0); + MsgId highlightId = 0, + const TextWithEntities &highlightPart = {}); explicit RepliesMemento( not_null commentsItem, MsgId commentId = 0); @@ -421,9 +425,12 @@ public: [[nodiscard]] not_null list() { return &_list; } - [[nodiscard]] MsgId getHighlightId() const { + [[nodiscard]] MsgId highlightId() const { return _highlightId; } + [[nodiscard]] const TextWithEntities &highlightPart() const { + return _highlightPart; + } private: void setupTopicViewer(); @@ -431,6 +438,7 @@ private: const not_null _history; MsgId _rootId = 0; const MsgId _highlightId = 0; + const TextWithEntities _highlightPart; ListMemento _list; std::shared_ptr _replies; QVector _replyReturns; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 1ee20e414..3f9314c40 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -274,9 +274,15 @@ void ScheduledWidget::setupComposeControls() { sendInlineResult(chosen.result, chosen.bot); }, lifetime()); - _composeControls->scrollRequests( - ) | rpl::start_with_next([=](Data::MessagePosition pos) { - showAtPosition(pos); + _composeControls->jumpToItemRequests( + ) | rpl::start_with_next([=](FullReplyTo to) { + if (const auto item = session().data().message(to.messageId)) { + if (item->isScheduled() && item->history() == _history) { + showAtPosition(item->position()); + } else { + JumpToMessageClickHandler(item, {}, to.quote)->onClick({}); + } + } }, lifetime()); _composeControls->scrollKeyEvents( @@ -858,7 +864,7 @@ void ScheduledWidget::showAtPosition( FullMsgId originId) { _inner->showAtPosition( position, - anim::type::normal, + {}, _cornerButtons.doneJumpFrom(position.fullId, originId)); } diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index a3d6550c5..0705ff2a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -669,23 +669,12 @@ TextForMimeData Service::selectedText(TextSelection selection) const { return text().toTextForMimeData(selection); } -TextWithEntities Service::selectedQuote(TextSelection selection) const { - return {}; -} - -TextWithEntities Service::selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const { +SelectedQuote Service::selectedQuote(TextSelection selection) const { return {}; } TextSelection Service::selectionFromQuote( - const TextWithEntities "e) const { - return {}; -} - -TextSelection Service::selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const { return {}; } diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index c862ce657..617dd1adb 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -43,14 +43,9 @@ public: StateRequest request) const override; void updatePressed(QPoint point) override; TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; - TextWithEntities selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( - const TextWithEntities "e) const override; - TextSelection selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const override; TextSelection adjustSelection( TextSelection selection, diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 5e45b8877..06825b37d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -744,6 +744,7 @@ void Document::draw( if (const auto captioned = Get()) { p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, captioned->caption); + auto highlightRequest = context.computeHighlightCache(); captioned->caption.draw(p, { .position = { st::msgPadding.left(), captiontop }, .availableWidth = captionw, @@ -756,6 +757,7 @@ void Document::draw( .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } } @@ -1210,7 +1212,7 @@ TextForMimeData Document::selectedText(TextSelection selection) const { return result; } -TextWithEntities Document::selectedQuote(TextSelection selection) const { +SelectedQuote Document::selectedQuote(TextSelection selection) const { if (const auto voice = Get()) { const auto length = voice->transcribeText.length(); if (selection.from < length) { @@ -1221,16 +1223,21 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const { voice->transcribeText); } if (const auto captioned = Get()) { - return parent()->selectedQuote(captioned->caption, selection); + return Element::FindSelectedQuote( + captioned->caption, + selection, + _realParent); } return {}; } TextSelection Document::selectionFromQuote( + not_null item, const TextWithEntities "e) const { if (const auto captioned = Get()) { - const auto result = parent()->selectionFromQuote( + const auto result = Element::FindSelectionFromQuote( captioned->caption, + item, quote); if (result.empty()) { return {}; @@ -1390,6 +1397,8 @@ void Document::drawGrouped( float64 highlightOpacity, not_null cacheKey, not_null cache) const { + const auto maybeMediaHighlight = context.highlightPathCache + && context.highlightPathCache->isEmpty(); p.translate(geometry.topLeft()); draw( p, @@ -1397,6 +1406,10 @@ void Document::drawGrouped( geometry.width(), LayoutMode::Grouped, rounding); + if (maybeMediaHighlight + && !context.highlightPathCache->isEmpty()) { + context.highlightPathCache->translate(geometry.topLeft()); + } p.translate(-geometry.topLeft()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 31dd9886a..ec2dc0d78 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -46,8 +46,9 @@ public: bool hasTextForCopy() const override; TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 6c259748d..a82fd13fd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -229,6 +229,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { if (!_caption.isEmpty()) { p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, _caption); + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint( st::msgPadding.left(), @@ -243,6 +244,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (!inWebPage) { auto fullRight = paintx + paintw; diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index a91f8350d..24ba5187c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/cached_round_corners.h" #include "ui/chat/chat_style.h" +#include "ui/effects/ripple_animation.h" #include "ui/painter.h" #include "ui/power_saving.h" #include "core/ui_integration.h" @@ -226,6 +227,13 @@ void Game::draw(Painter &p, const PaintContext &context) const { Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); + if (_ripple) { + _ripple->paint(p, outer.x(), outer.y(), width(), &cache->bg); + if (_ripple->empty()) { + _ripple = nullptr; + } + } + auto lineHeight = UnitedLineHeight(); if (_titleLines) { p.setPen(cache->icon); @@ -322,7 +330,6 @@ TextState Game::textState(QPoint point, StateRequest request) const { auto tshift = inner.top(); auto paintw = inner.width(); - auto inThumb = false; auto symbolAdd = 0; auto lineHeight = UnitedLineHeight(); if (_titleLines) { @@ -353,11 +360,7 @@ TextState Game::textState(QPoint point, StateRequest request) const { } tshift += _descriptionLines * lineHeight; } - if (inThumb) { - if (_parent->data()->isHistoryEntry()) { - result.link = _openl; - } - } else if (_attach) { + if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; @@ -375,6 +378,12 @@ TextState Game::textState(QPoint point, StateRequest request) const { } } } + if (_parent->data()->isHistoryEntry()) { + if (!result.link && outer.contains(point)) { + result.link = _openl; + } + } + _lastPoint = point - outer.topLeft(); result.symbol += symbolAdd; return result; @@ -399,11 +408,41 @@ void Game::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { } void Game::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (p == _openl) { + if (pressed) { + if (!_ripple) { + const auto full = QRect(0, 0, width(), height()); + const auto outer = full.marginsRemoved(inBubblePadding()); + const auto owner = &parent()->history()->owner(); + _ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + outer.size(), + _st.radius), + [=] { owner->requestViewRepaint(parent()); }); + } + _ripple->add(_lastPoint); + } else if (_ripple) { + _ripple->lastStop(); + } + } if (_attach) { _attach->clickHandlerPressedChanged(p, pressed); } } +bool Game::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const { + return _attach && _attach->toggleSelectionByHandlerClick(p); +} + +bool Game::allowTextSelectionByHandler(const ClickHandlerPtr &p) const { + return (p == _openl); +} + +bool Game::dragItemByHandler(const ClickHandlerPtr &p) const { + return _attach && _attach->dragItemByHandler(p); +} + TextForMimeData Game::selectedText(TextSelection selection) const { auto titleResult = _title.toTextForMimeData(selection); auto descriptionResult = _description.toTextForMimeData( diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.h b/Telegram/SourceFiles/history/view/media/history_view_game.h index 9cec01fdc..b7618bf69 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.h +++ b/Telegram/SourceFiles/history/view/media/history_view_game.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class ReplyMarkupClickHandler; +namespace Ui { +class RippleAnimation; +} // namespace Ui + namespace HistoryView { class Game : public Media { @@ -35,12 +39,11 @@ public: return false; // we do not add _title and _description in FullSelection text copy. } - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return _attach && _attach->toggleSelectionByHandlerClick(p); - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return _attach && _attach->dragItemByHandler(p); - } + bool toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const override; + bool allowTextSelectionByHandler( + const ClickHandlerPtr &p) const override; + bool dragItemByHandler(const ClickHandlerPtr &p) const override; TextForMimeData selectedText(TextSelection selection) const override; @@ -102,7 +105,9 @@ private: const not_null _data; std::shared_ptr _openl; std::unique_ptr _attach; + mutable std::unique_ptr _ripple; + mutable QPoint _lastPoint; int _gameTagWidth = 0; int _descriptionLines = 0; int _titleLines = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 70a81264d..340b1fa66 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -716,6 +716,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { _parent->width()); top += botTop->height; } + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint(st::msgPadding.left(), top), .availableWidth = captionw, @@ -728,6 +729,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (!inWebPage && !skipDrawingSurrounding) { auto fullRight = paintx + usex + usew; @@ -1203,13 +1205,14 @@ TextForMimeData Gif::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } -TextWithEntities Gif::selectedQuote(TextSelection selection) const { - return parent()->selectedQuote(_caption, selection); +SelectedQuote Gif::selectedQuote(TextSelection selection) const { + return Element::FindSelectedQuote(_caption, selection, _realParent); } TextSelection Gif::selectionFromQuote( + not_null item, const TextWithEntities "e) const { - return parent()->selectionFromQuote(_caption, quote); + return Element::FindSelectionFromQuote(_caption, item, quote); } bool Gif::fullFeaturedGrouped(RectParts sides) const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index fdba9a0af..eac33cc7a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -68,8 +68,9 @@ public: } TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 4e19fa291..8f9923c36 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -181,6 +181,11 @@ Storage::SharedMediaTypesMask Media::sharedMediaTypes() const { return {}; } +bool Media::allowTextSelectionByHandler( + const ClickHandlerPtr &handler) const { + return false; +} + not_null Media::parent() const { return _parent; } @@ -189,6 +194,10 @@ not_null Media::history() const { return _parent->history(); } +SelectedQuote Media::selectedQuote(TextSelection selection) const { + return {}; +} + bool Media::isDisplayed() const { return true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index bda57224e..7dadb5f87 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -49,6 +49,7 @@ struct StateRequest; struct MediaSpoiler; class StickerPlayer; class Element; +struct SelectedQuote; using PaintContext = Ui::ChatPaintContext; @@ -88,11 +89,10 @@ public: TextSelection selection) const { return {}; } - [[nodiscard]] virtual TextWithEntities selectedQuote( - TextSelection selection) const { - return {}; - } + [[nodiscard]] virtual SelectedQuote selectedQuote( + TextSelection selection) const; [[nodiscard]] virtual TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const { return {}; } @@ -136,6 +136,8 @@ public: // toggle selection instead of activating the pressed link [[nodiscard]] virtual bool toggleSelectionByHandlerClick( const ClickHandlerPtr &p) const = 0; + [[nodiscard]] virtual bool allowTextSelectionByHandler( + const ClickHandlerPtr &p) const; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 79cbcf869..bea617b23 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -286,19 +286,45 @@ void GroupedMedia::drawHighlight( Painter &p, const PaintContext &context, int top) const { - if (_mode != Mode::Column) { + if (context.highlight.opacity == 0.) { return; } + auto selection = context.highlight.range; + if (_mode != Mode::Column) { + if (!selection.empty() && !IsSubGroupSelection(selection)) { + _parent->paintCustomHighlight( + p, + context, + top, + height(), + _parent->data().get()); + } + return; + } + const auto empty = selection.empty(); + const auto subpart = IsSubGroupSelection(selection); const auto skip = top + groupedPadding().top(); for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; const auto rect = part.geometry.translated(0, skip); - _parent->paintCustomHighlight( - p, - context, - rect.y(), - rect.height(), - part.item); + const auto full = (!i && empty) + || (subpart && IsGroupItemSelection(selection, i)) + || (!subpart + && !selection.empty() + && (selection.from < part.content->fullSelectionLength())); + if (!subpart) { + selection = part.content->skipSelection(selection); + } + if (full) { + auto copy = context; + copy.highlight.range = {}; + _parent->paintCustomHighlight( + p, + copy, + rect.y(), + rect.height(), + part.item); + } } } @@ -316,21 +342,31 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { const auto rounding = inWebPage ? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall } : adjustedBubbleRoundingWithCaption(_caption); + auto highlight = context.highlight.range; + const auto subpartHighlight = IsSubGroupSelection(highlight); for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; - const auto partContext = context.withSelection(fullSelection + auto partContext = context.withSelection(fullSelection ? FullSelection : textSelection ? selection : IsGroupItemSelection(selection, i) ? FullSelection : TextSelection()); + const auto highlighted = (highlight.empty() && !i) + || IsGroupItemSelection(highlight, i); + const auto highlightOpacity = highlighted + ? context.highlight.opacity + : 0.; + partContext.highlight.range = highlighted + ? TextSelection() + : highlight; if (textSelection) { selection = part.content->skipSelection(selection); } - const auto highlightOpacity = (_mode == Mode::Grid) - ? _parent->delegate()->elementHighlightOpacity(part.item) - : 0.; + if (!subpartHighlight) { + highlight = part.content->skipSelection(highlight); + } if (!part.cache.isNull()) { wasCache = true; } @@ -361,6 +397,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { const auto stm = context.messageStyle(); p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, _caption); + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint( st::msgPadding.left(), @@ -375,6 +412,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (_parent->media() == this) { auto fullRight = width(); @@ -514,6 +552,7 @@ TextSelection GroupedMedia::adjustSelection( selection.to = modified.to; return selection; } + checked = till; } return selection; } @@ -561,6 +600,50 @@ TextForMimeData GroupedMedia::selectedText( return result; } +SelectedQuote GroupedMedia::selectedQuote(TextSelection selection) const { + if (_mode != Mode::Column) { + return _captionItem + ? Element::FindSelectedQuote(_caption, selection, _captionItem) + : SelectedQuote(); + } + for (const auto &part : _parts) { + const auto next = part.content->skipSelection(selection); + if (next.to - next.from != selection.to - selection.from) { + if (!next.empty()) { + return SelectedQuote(); + } + auto result = part.content->selectedQuote(selection); + result.item = part.item; + return result; + } + selection = next; + } + return {}; +} + +TextSelection GroupedMedia::selectionFromQuote( + not_null item, + const TextWithEntities "e) const { + if (_mode != Mode::Column) { + return (_captionItem == item) + ? Element::FindSelectionFromQuote(_caption, item, quote) + : TextSelection(); + } + const auto i = ranges::find(_parts, item, &Part::item); + if (i == end(_parts)) { + return {}; + } + const auto index = int(i - begin(_parts)); + auto result = i->content->selectionFromQuote(item, quote); + if (result.empty()) { + return AddGroupItemSelection({}, index); + } + for (auto j = i; j != begin(_parts);) { + result = (--j)->content->unskipSelection(result); + } + return result; +} + auto GroupedMedia::getBubbleSelectionIntervals( TextSelection selection) const -> std::vector { @@ -663,16 +746,15 @@ bool GroupedMedia::validateGroupParts( } void GroupedMedia::refreshCaption() { - using PartPtrOpt = std::optional; - const auto captionPart = [&]() -> PartPtrOpt { + const auto part = [&]() -> const Part* { if (_mode == Mode::Column) { - return std::nullopt; + return nullptr; } - auto result = PartPtrOpt(); + auto result = (const Part*)nullptr; for (const auto &part : _parts) { if (!part.item->emptyText()) { if (result) { - return std::nullopt; + return nullptr; } else { result = ∂ } @@ -680,8 +762,7 @@ void GroupedMedia::refreshCaption() { } return result; }(); - if (captionPart) { - const auto &part = (*captionPart); + if (part) { _caption = createCaption(part->item); _captionItem = part->item; } else { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index c66864063..b6fbc77d9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -55,6 +55,10 @@ public: DocumentData *getDocument() const override; TextForMimeData selectedText(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; + TextSelection selectionFromQuote( + not_null item, + const TextWithEntities "e) const override; std::vector getBubbleSelectionIntervals( TextSelection selection) const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 413046ed3..69f4875e1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -104,6 +104,9 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { const auto surroundingWidth = _additionalOnTop ? std::min(newWidth - st::msgReplyPadding.left(), additional) : (newWidth - _contentSize.width() - st::msgReplyPadding.left()); + if (reply) { + [[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth); + } const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth); if (_additionalOnTop) { _topAdded = surrounding.height + st::msgMargin.bottom(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 81dee1c54..de72e7786 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -401,6 +401,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { _parent->width()); top += botTop->height; } + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint(st::msgPadding.left(), top), .availableWidth = captionw, @@ -413,6 +414,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (!inWebPage) { auto fullRight = paintx + paintw; @@ -1049,13 +1051,14 @@ TextForMimeData Photo::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } -TextWithEntities Photo::selectedQuote(TextSelection selection) const { - return parent()->selectedQuote(_caption, selection); +SelectedQuote Photo::selectedQuote(TextSelection selection) const { + return Element::FindSelectedQuote(_caption, selection, _realParent); } TextSelection Photo::selectionFromQuote( + not_null item, const TextWithEntities "e) const { - return parent()->selectionFromQuote(_caption, quote); + return Element::FindSelectionFromQuote(_caption, item, quote); } void Photo::hideSpoilers() { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index b76bbe264..7213dca21 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -57,8 +57,9 @@ public: } TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const override; PhotoData *getPhoto() const override { diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 38b6dc258..a6bb07528 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -155,7 +155,7 @@ bool WebPage::HasButton(not_null webpage) { } QSize WebPage::countOptimalSize() { - if (_data->pendingTill) { + if (_data->pendingTill || _data->failed) { return { 0, 0 }; } @@ -366,7 +366,7 @@ QSize WebPage::countOptimalSize() { } QSize WebPage::countCurrentSize(int newWidth) { - if (_data->pendingTill) { + if (_data->pendingTill || _data->failed) { return { newWidth, minHeight() }; } @@ -891,6 +891,7 @@ void WebPage::playAnimation(bool autoplay) { bool WebPage::isDisplayed() const { const auto item = _parent->data(); return !_data->pendingTill + && !_data->failed && !item->Has(); } @@ -903,6 +904,11 @@ bool WebPage::toggleSelectionByHandlerClick( return _attach && _attach->toggleSelectionByHandlerClick(p); } +bool WebPage::allowTextSelectionByHandler( + const ClickHandlerPtr &p) const { + return (p == _openl); +} + bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const { return _attach && _attach->dragItemByHandler(p); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index 0946714ff..bf9254e65 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -50,6 +50,8 @@ public: bool toggleSelectionByHandlerClick( const ClickHandlerPtr &p) const override; + bool allowTextSelectionByHandler( + const ClickHandlerPtr &p) const override; bool dragItemByHandler(const ClickHandlerPtr &p) const override; TextForMimeData selectedText(TextSelection selection) const override; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 2822879fd..6bfc8c099 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_actions.h" +#include "base/options.h" #include "data/data_peer_values.h" #include "data/data_session.h" #include "data/data_folder.h" @@ -77,6 +78,12 @@ namespace Info { namespace Profile { namespace { +base::options::toggle ShowPeerIdBelowAbout({ + .id = kOptionShowPeerIdBelowAbout, + .name = "Show Peer IDs in Profile", + .description = "Show peer IDs from API below their Bio / Description.", +}); + [[nodiscard]] rpl::producer UsernamesSubtext( not_null peer, rpl::producer fallback) { @@ -148,6 +155,27 @@ namespace { return result; } +[[nodiscard]] rpl::producer AboutWithIdValue( + not_null peer) { + + return AboutValue( + peer + ) | rpl::map([=](TextWithEntities &&value) { + if (!ShowPeerIdBelowAbout.value()) { + return std::move(value); + } + using namespace Ui::Text; + if (!value.empty()) { + value.append("\n"); + } + value.append(Italic(u"id: "_q)); + const auto raw = peer->id.value & PeerId::kChatTypeMask; + const auto id = QString::number(raw); + value.append(Link(Italic(id), "internal:copy:" + id)); + return std::move(value); + }); +} + template auto AddActionButton( not_null parent, @@ -425,8 +453,8 @@ object_ptr DetailsFiller::setupInfo() { ? tr::lng_info_about_label() : tr::lng_info_bio_label(); addTranslateToMenu( - addInfoLine(std::move(label), AboutValue(user)).text, - AboutValue(user)); + addInfoLine(std::move(label), AboutWithIdValue(user)).text, + AboutWithIdValue(user)); const auto usernameLine = addInfoOneLine( UsernamesSubtext(_peer, tr::lng_info_username_label()), @@ -576,11 +604,11 @@ object_ptr DetailsFiller::setupInfo() { ).text->setLinksTrusted(); } - const auto about = addInfoLine( - tr::lng_info_about_label(), - _topic ? rpl::single(TextWithEntities()) : AboutValue(_peer)); + const auto about = addInfoLine(tr::lng_info_about_label(), _topic + ? rpl::single(TextWithEntities()) + : AboutWithIdValue(_peer)); if (!_topic) { - addTranslateToMenu(about.text, AboutValue(_peer)); + addTranslateToMenu(about.text, AboutWithIdValue(_peer)); } if (settings->showPeerId != 0 && !_topic) @@ -1129,6 +1157,8 @@ object_ptr ActionsFiller::fill() { } // namespace +const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about"; + object_ptr SetupDetails( not_null controller, not_null parent, diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.h b/Telegram/SourceFiles/info/profile/info_profile_actions.h index 3dfae7358..fcbc351f5 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.h +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.h @@ -23,6 +23,8 @@ class Controller; namespace Info::Profile { +extern const char kOptionShowPeerIdBelowAbout[]; + object_ptr SetupDetails( not_null controller, not_null parent, diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index cc9702e78..878366d15 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_values.h" -#include "base/options.h" #include "info/profile/info_profile_badge.h" #include "core/application.h" #include "core/click_handler_types.h" @@ -38,13 +37,6 @@ namespace { using UpdateFlag = Data::PeerUpdate::Flag; -base::options::toggle ShowPeerIdBelowAbout({ - .id = kOptionShowPeerIdBelowAbout, - .name = "Show Peer IDs in Profile", - .description = "Show peer IDs from API below their Bio / Description.", - .scope = static_cast(0), -}); - auto PlainAboutValue(not_null peer) { return peer->session().changes().peerFlagsValue( peer, @@ -95,8 +87,6 @@ void StripExternalLinks(TextWithEntities &text) { } // namespace -const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about"; - rpl::producer NameValue(not_null peer) { return peer->session().changes().peerFlagsValue( peer, @@ -219,16 +209,6 @@ TextWithEntities AboutWithEntities( if (stripExternal) { StripExternalLinks(result); } - if (ShowPeerIdBelowAbout.value()) { - using namespace Ui::Text; - if (!result.empty()) { - result.append("\n"); - } - result.append(Italic(u"id: "_q)); - const auto raw = peer->id.value & PeerId::kChatTypeMask; - const auto id = QString::number(raw); - result.append(Link(Italic(id), "internal:copy:" + id)); - } return result; } diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 67c9238fb..b3a110fe4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -35,8 +35,6 @@ enum class SharedMediaType : signed char; namespace Info::Profile { -extern const char kOptionShowPeerIdBelowAbout[]; - inline auto ToSingleLine() { return rpl::map([](const QString &text) { return TextUtilities::SingleLine(text); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp index c90ee395c..baa569ab3 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_statistics.h" #include "apiwrap.h" +#include "base/event_filter.h" #include "data/data_peer.h" #include "data/data_session.h" #include "history/history_item.h" @@ -28,9 +29,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rect.h" #include "ui/toast/toast.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" @@ -43,6 +46,39 @@ struct Descriptor final { not_null toastParent; }; +void AddContextMenu( + not_null button, + not_null controller, + not_null item) { + const auto fullId = item->fullId(); + const auto contextMenu = button->lifetime() + .make_state>(); + const auto showMenu = [=] { + *contextMenu = base::make_unique_q( + button, + st::popupMenuWithIcons); + const auto go = [=] { + const auto &session = controller->parentController(); + if (const auto item = session->session().data().message(fullId)) { + session->showMessage(item); + } + }; + contextMenu->get()->addAction( + tr::lng_context_to_msg(tr::now), + crl::guard(controller, go), + &st::menuIconShowInChat); + contextMenu->get()->popup(QCursor::pos()); + }; + + base::install_event_filter(button, [=](not_null e) { + if (e->type() == QEvent::ContextMenu) { + showMenu(); + return base::EventFilterResult::Cancel; + } + return base::EventFilterResult::Continue; + }); +} + void ProcessZoom( const Descriptor &d, not_null widget, @@ -544,7 +580,9 @@ void InnerWidget::fill() { if (_state.stats.message) { if (const auto i = _peer->owner().message(_contextId)) { ::Settings::AddSkip(inner); - inner->add(object_ptr(this, i, -1, -1, QImage())); + const auto preview = inner->add( + object_ptr(this, i, -1, -1, QImage())); + AddContextMenu(preview, _controller, i); ::Settings::AddSkip(inner); ::Settings::AddDivider(inner); } @@ -639,6 +677,8 @@ void InnerWidget::fillRecentPosts() { info.forwardsCount, std::move(cachedPreview)); + AddContextMenu(button, _controller, item); + _messagePreviews.push_back(raw); raw->show(); button->sizeValue( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 29842290b..5a4bc0a93 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -828,6 +828,19 @@ void AttachWebView::requestWithOptionalConfirm( void AttachWebView::request(const WebViewButton &button) { Expects(_context != nullptr && _bot != nullptr); + if (button.fromAttachMenu) { + const auto bot = ranges::find( + _attachBots, + not_null{ _bot }, + &AttachWebViewBot::user); + if (bot == end(_attachBots) || bot->inactive) { + requestAddToMenu(_bot, AddToMenuOpenAttach{ + .startCommand = button.startCommand, + }); + return; + } + } + _startCommand = button.startCommand; const auto &action = _context->action; @@ -1524,8 +1537,10 @@ void AttachWebView::confirmAddToMenu( } _confirmAddBox = active->show(Box([=](not_null box) { const auto allowed = std::make_shared(); + const auto disclaimer = !disclaimerAccepted(bot); const auto done = [=](Fn close) { - const auto state = ((*allowed) && (*allowed)->checked()) + const auto state = (disclaimer + || ((*allowed) && (*allowed)->checked())) ? ToggledState::AllowedToWrite : ToggledState::Added; toggleInMenu(bot.user, state, [=] { @@ -1538,13 +1553,22 @@ void AttachWebView::confirmAddToMenu( }); close(); }; - const auto disclaimer = !disclaimerAccepted(bot); if (disclaimer) { FillDisclaimerBox(box, [=] { _disclaimerAccepted.emplace(bot.user); _attachBotsUpdates.fire({}); done([] {}); }); + box->addRow(object_ptr( + box, + st::boxRowPadding.left())); + box->addRow(object_ptr( + box, + tr::lng_bot_will_be_added( + lt_bot, + rpl::single(Ui::Text::Bold(bot.name)), + Ui::Text::WithEntities), + st::boxLabel)); } else { Ui::ConfirmBox(box, { (bot.inMainMenu @@ -1556,40 +1580,26 @@ void AttachWebView::confirmAddToMenu( Ui::Text::WithEntities), done, }); - } - if (bot.requestWriteAccess) { - (*allowed) = box->addRow( - object_ptr( - box, - tr::lng_url_auth_allow_messages( - tr::now, - lt_bot, - Ui::Text::Bold(bot.name), - Ui::Text::WithEntities), - true, - st::urlAuthCheckbox), - style::margins( - st::boxRowPadding.left(), - (disclaimer - ? st::boxPhotoCaptionSkip - : st::boxRowPadding.left()), - st::boxRowPadding.right(), - st::boxRowPadding.left())); - (*allowed)->setAllowTextLines(); - } - if (disclaimer) { - if (!bot.requestWriteAccess) { - box->addRow(object_ptr( - box, - st::boxRowPadding.left())); + if (bot.requestWriteAccess) { + (*allowed) = box->addRow( + object_ptr( + box, + tr::lng_url_auth_allow_messages( + tr::now, + lt_bot, + Ui::Text::Bold(bot.name), + Ui::Text::WithEntities), + true, + st::urlAuthCheckbox), + style::margins( + st::boxRowPadding.left(), + (disclaimer + ? st::boxPhotoCaptionSkip + : st::boxRowPadding.left()), + st::boxRowPadding.right(), + st::boxRowPadding.left())); + (*allowed)->setAllowTextLines(); } - box->addRow(object_ptr( - box, - tr::lng_bot_will_be_added( - lt_bot, - rpl::single(Ui::Text::Bold(bot.name)), - Ui::Text::WithEntities), - st::boxLabel)); } })); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index ce8a167e4..a389c9627 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -134,6 +134,9 @@ public: [[nodiscard]] rpl::producer<> attachBotsUpdates() const { return _attachBotsUpdates.events(); } + void notifyBotIconLoaded() { + _attachBotsUpdates.fire({}); + } [[nodiscard]] bool disclaimerAccepted( const AttachWebViewBot &bot) const; [[nodiscard]] bool showMainMenuNewBadge( diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4cc65c77a..2540e2c98 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1411,7 +1411,7 @@ void MainWidget::showHistory( && way != Way::Forward) { clearBotStartToken(_history->peer()); } - _history->showHistory(peerId, showAtMsgId); + _history->showHistory(peerId, showAtMsgId, params.highlightPart); if (alreadyThatPeer && params.reapplyLocalDraft) { _history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry); } @@ -1772,7 +1772,7 @@ void MainWidget::showNewSection( } else { _mainSection = std::move(newMainSection); _history->finishAnimating(); - _history->showHistory(0, 0); + _history->showHistory(0, 0, {}); if (const auto entry = _mainSection->activeChat(); entry.key) { _controller->setActiveChatEntry(entry); diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.h b/Telegram/SourceFiles/platform/linux/tray_linux.h index dd0175a2c..769fe4409 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.h +++ b/Telegram/SourceFiles/platform/linux/tray_linux.h @@ -67,4 +67,8 @@ private: }; +inline bool HasMonochromeSetting() { + return false; +} + } // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm index e02930c1a..984643871 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "mainwidget.h" +#include "calls/calls_instance.h" #include "core/sandbox.h" #include "core/application.h" #include "core/core_settings.h" @@ -175,7 +176,11 @@ ApplicationDelegate *_sharedDelegate = nil; Core::App().handleAppActivated(); if (const auto window = Core::App().activeWindow()) { if (window->widget()->isHidden()) { - window->widget()->showFromTray(); + if (Core::App().calls().hasVisiblePanel()) { + Core::App().calls().activateCurrentCall(); + } else { + window->widget()->showFromTray(); + } } } } diff --git a/Telegram/SourceFiles/platform/mac/tray_mac.h b/Telegram/SourceFiles/platform/mac/tray_mac.h index 50014bea2..6667d3cb1 100644 --- a/Telegram/SourceFiles/platform/mac/tray_mac.h +++ b/Telegram/SourceFiles/platform/mac/tray_mac.h @@ -55,4 +55,8 @@ private: }; +inline bool HasMonochromeSetting() { + return false; +} + } // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_tray.h b/Telegram/SourceFiles/platform/platform_tray.h index 0838b52e4..20deabebf 100644 --- a/Telegram/SourceFiles/platform/platform_tray.h +++ b/Telegram/SourceFiles/platform/platform_tray.h @@ -11,6 +11,8 @@ namespace Platform { class Tray; +[[nodiscard]] bool HasMonochromeSetting(); + } // namespace Platform // Platform dependent implementations. diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp index 4bc78e6df..49d3cccac 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.cpp +++ b/Telegram/SourceFiles/platform/win/tray_win.cpp @@ -233,18 +233,19 @@ void Tray::updateIcon() { : !controller->sessionController() ? nullptr : &controller->sessionController()->session(); + const auto monochrome = Core::App().settings().trayIconMonochrome(); const auto supportMode = session && session->supportMode(); const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON); auto iconSmallPixmap16 = Tray::IconWithCounter( CounterLayerArgs(16, counter, muted), true, - true, + monochrome, supportMode); auto iconSmallPixmap32 = Tray::IconWithCounter( CounterLayerArgs(32, counter, muted), true, - true, + monochrome, supportMode); auto iconSmall = QIcon(); iconSmall.addPixmap(iconSmallPixmap16); @@ -351,8 +352,15 @@ QPixmap Tray::IconWithCounter( bool smallIcon, bool monochrome, bool supportMode) { - return Ui::PixmapFromImage( - ImageIconWithCounter(std::move(args), supportMode, smallIcon, monochrome)); + return Ui::PixmapFromImage(ImageIconWithCounter( + std::move(args), + supportMode, + smallIcon, + monochrome)); +} + +bool HasMonochromeSetting() { + return IsDarkTaskbar().has_value(); } } // namespace Platform diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 60c6ed1e3..54975af31 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -453,18 +453,35 @@ void SetupSystemIntegrationContent( st::settingsCheckboxPadding)); }; + const auto settings = &Core::App().settings(); if (Platform::TrayIconSupported()) { - const auto trayEnabled = [] { - const auto workMode = Core::App().settings().workMode(); + const auto trayEnabled = [=] { + const auto workMode = settings->workMode(); return (workMode == WorkMode::TrayOnly) || (workMode == WorkMode::WindowAndTray); }; const auto tray = addCheckbox( tr::lng_settings_workmode_tray(), trayEnabled()); + const auto monochrome = Platform::HasMonochromeSetting() + ? addSlidingCheckbox( + tr::lng_settings_monochrome_icon(), + settings->trayIconMonochrome()) + : nullptr; + if (monochrome) { + monochrome->toggle(tray->checked(), anim::type::instant); - const auto taskbarEnabled = [] { - const auto workMode = Core::App().settings().workMode(); + monochrome->entity()->checkedChanges( + ) | rpl::filter([=](bool value) { + return (value != settings->trayIconMonochrome()); + }) | rpl::start_with_next([=](bool value) { + settings->setTrayIconMonochrome(value); + Core::App().saveSettingsDelayed(); + }, monochrome->lifetime()); + } + + const auto taskbarEnabled = [=] { + const auto workMode = settings->workMode(); return (workMode == WorkMode::WindowOnly) || (workMode == WorkMode::WindowAndTray); }; @@ -482,10 +499,10 @@ void SetupSystemIntegrationContent( : WorkMode::WindowOnly; if ((newMode == WorkMode::WindowAndTray || newMode == WorkMode::TrayOnly) - && Core::App().settings().workMode() != newMode) { + && settings->workMode() != newMode) { cSetSeenTrayTooltip(false); } - Core::App().settings().setWorkMode(newMode); + settings->setWorkMode(newMode); Core::App().saveSettingsDelayed(); }; @@ -498,6 +515,9 @@ void SetupSystemIntegrationContent( } else { updateWorkmode(); } + if (monochrome) { + monochrome->toggle(checked, anim::type::normal); + } }, tray->lifetime()); if (taskbar) { @@ -519,19 +539,19 @@ void SetupSystemIntegrationContent( tr::lng_settings_mac_warn_before_quit( lt_text, rpl::single(Platform::ConfirmQuit::QuitKeysString())), - Core::App().settings().macWarnBeforeQuit()); + settings->macWarnBeforeQuit()); warnBeforeQuit->checkedChanges( ) | rpl::filter([=](bool checked) { - return (checked != Core::App().settings().macWarnBeforeQuit()); + return (checked != settings->macWarnBeforeQuit()); }) | rpl::start_with_next([=](bool checked) { - Core::App().settings().setMacWarnBeforeQuit(checked); + settings->setMacWarnBeforeQuit(checked); Core::App().saveSettingsDelayed(); }, warnBeforeQuit->lifetime()); #ifndef OS_MAC_STORE - const auto enabled = [] { + const auto enabled = [=] { const auto digest = base::Platform::CurrentCustomAppIconDigest(); - return digest && (Core::App().settings().macRoundIconDigest() == digest); + return digest && (settings->macRoundIconDigest() == digest); }; const auto roundIcon = addCheckbox( tr::lng_settings_mac_round_icon(), @@ -548,7 +568,7 @@ void SetupSystemIntegrationContent( } Window::OverrideApplicationIcon(checked ? IconMacRound() : QImage()); Core::App().refreshApplicationIcon(); - Core::App().settings().setMacRoundIconDigest(digest); + settings->setMacRoundIconDigest(digest); Core::App().saveSettings(); }, roundIcon->lifetime()); #endif // OS_MAC_STORE @@ -557,10 +577,10 @@ void SetupSystemIntegrationContent( if (!Platform::RunInBackground()) { const auto closeToTaskbar = addSlidingCheckbox( tr::lng_settings_close_to_taskbar(), - Core::App().settings().closeToTaskbar()); + settings->closeToTaskbar()); const auto closeToTaskbarShown = std::make_shared>(false); - Core::App().settings().workModeValue( + settings->workModeValue( ) | rpl::start_with_next([=](WorkMode workMode) { *closeToTaskbarShown = !Core::App().tray().has(); }, closeToTaskbar->lifetime()); @@ -568,9 +588,9 @@ void SetupSystemIntegrationContent( closeToTaskbar->toggleOn(closeToTaskbarShown->value()); closeToTaskbar->entity()->checkedChanges( ) | rpl::filter([=](bool checked) { - return (checked != Core::App().settings().closeToTaskbar()); + return (checked != settings->closeToTaskbar()); }) | rpl::start_with_next([=](bool checked) { - Core::App().settings().setCloseToTaskbar(checked); + settings->setCloseToTaskbar(checked); Local::writeSettings(); }, closeToTaskbar->lifetime()); } diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 6104cabb1..c6a9b5cde 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/launcher.h" #include "chat_helpers/tabbed_panel.h" #include "dialogs/dialogs_widget.h" -#include "info/profile/info_profile_values.h" +#include "info/profile/info_profile_actions.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "media/player/media_player_instance.h" diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index a6a167185..cead2c80f 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -1018,9 +1018,9 @@ void ChartWidget::updateBottomDates() { const auto k = _chartArea->width() / d; const auto stepRaw = int(k / 6); + const auto by = int(_chartArea->width() / float64(_chartData.x.size())); _bottomLine.captionIndicesOffset = 0 - + st::statisticsChartBottomCaptionMaxWidth - / int(_chartArea->width() / float64(_chartData.x.size())); + + st::statisticsChartBottomCaptionMaxWidth / std::max(by, 1); const auto isCurrentNull = (_bottomLine.current.stepMinFast == 0); if (!isCurrentNull diff --git a/Telegram/SourceFiles/tray.cpp b/Telegram/SourceFiles/tray.cpp index 7e3bcf22e..09d0fdfa9 100644 --- a/Telegram/SourceFiles/tray.cpp +++ b/Telegram/SourceFiles/tray.cpp @@ -46,6 +46,11 @@ void Tray::create() { } }, _tray.lifetime()); + Core::App().settings().trayIconMonochromeChanges( + ) | rpl::start_with_next([=] { + updateIconCounters(); + }, _tray.lifetime()); + Core::App().passcodeLockChanges( ) | rpl::start_with_next([=] { rebuildMenu(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 0f1e19cde..70018cad1 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -146,6 +146,7 @@ outReplyTextPaletteSelected: TextPalette(outTextPaletteSelected) { } imgReplyTextPalette: TextPalette(defaultTextPalette) { linkFg: msgImgReplyBarColor; + monoFg: msgImgReplyBarColor; } inSemiboldPalette: TextPalette(inTextPalette) { linkFg: msgInServiceFg; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 8ba606941..39f5a76e4 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -142,6 +142,12 @@ struct BackgroundEmojiData { uint8 colorIndexPlusOne); }; +struct ChatPaintHighlight { + float64 opacity = 0.; + float64 collapsion = 0.; + TextSelection range; +}; + struct ChatPaintContext { not_null st; const BubblePattern *bubblesPattern = nullptr; @@ -149,11 +155,15 @@ struct ChatPaintContext { QRect viewport; QRect clip; TextSelection selection; + ChatPaintHighlight highlight; + QPainterPath *highlightPathCache = nullptr; + mutable QRect highlightInterpolateTo; crl::time now = 0; void translate(int x, int y) { viewport.translate(x, y); clip.translate(x, y); + highlightInterpolateTo.translate(x, y); } void translate(QPoint point) { translate(point.x(), point.y()); @@ -181,6 +191,19 @@ struct ChatPaintContext { result.selection = selection; return result; } + [[nodiscard]] auto computeHighlightCache() const + -> std::optional { + if (highlight.range.empty() || highlight.collapsion <= 0.) { + return {}; + } + return Ui::Text::HighlightInfoRequest{ + .range = highlight.range, + .interpolateTo = highlightInterpolateTo, + .interpolateProgress = (1. - highlight.collapsion), + .outPath = highlightPathCache, + }; + }; + // This is supported only in unwrapped media for now. enum class SkipDrawingParts { diff --git a/Telegram/SourceFiles/ui/widgets/vertical_drum_picker.cpp b/Telegram/SourceFiles/ui/widgets/vertical_drum_picker.cpp index 9e546e004..d0cc71ca3 100644 --- a/Telegram/SourceFiles/ui/widgets/vertical_drum_picker.cpp +++ b/Telegram/SourceFiles/ui/widgets/vertical_drum_picker.cpp @@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_basic.h" namespace Ui { +namespace { + +constexpr auto kAlmostIndex = float64(.99); + +} // namespace PickerAnimation::PickerAnimation() = default; @@ -26,6 +31,23 @@ void PickerAnimation::jumpToOffset(int offset) { value); _updates.fire(_result.current - was); }; + if (anim::Disabled()) { + auto value = float64(0.); + const auto diff = _result.to - _result.from; + const auto step = std::min( + kAlmostIndex, + 1. / (std::max(1. - kAlmostIndex, std::abs(diff) + 1))); + while (true) { + value += step; + if (value >= 1.) { + callback(1.); + break; + } else { + callback(value); + } + } + return; + } _animation.start( std::move(callback), 0., @@ -94,20 +116,14 @@ VerticalDrumPicker::VerticalDrumPicker( }, lifetime()); _animation.updates( - ) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](PickerAnimation::Shift shift) { increaseShift(shift); - if (anim::Disabled()) { - animationDataFromIndex(); - _animation.jumpToOffset(0); - } }, lifetime()); } void VerticalDrumPicker::increaseShift(float64 by) { { // Guard input. - constexpr auto kAlmostIndex = .99; if (by >= 1.) { by = kAlmostIndex; } else if (by <= -1.) { diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 3eca7916b..bdf2e061b 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_config.h" +#include "data/data_document_media.h" #include "data/data_folder.h" #include "data/data_session.h" #include "data/data_user.h" @@ -216,6 +217,9 @@ void SetupMenuBots( const auto wrap = container->add( object_ptr(container)); const auto bots = &controller->session().attachWebView(); + const auto iconLoadLifetime = wrap->lifetime().make_state< + rpl::lifetime + >(); rpl::single( rpl::empty @@ -225,7 +229,20 @@ void SetupMenuBots( const auto width = container->widthNoMargins(); wrap->clear(); for (const auto &bot : bots->attachBots()) { - if (!bot.inMainMenu) { + const auto user = bot.user; + if (!bot.inMainMenu || !bot.media) { + continue; + } else if (const auto media = bot.media; !media->loaded()) { + if (!*iconLoadLifetime) { + auto &session = user->session(); + *iconLoadLifetime = session.downloaderTaskFinished( + ) | rpl::start_with_next([=] { + if (media->loaded()) { + iconLoadLifetime->destroy(); + bots->notifyBotIconLoaded(); + } + }); + } continue; } const auto button = Settings::AddButton( @@ -244,7 +261,6 @@ void SetupMenuBots( st::mainMenuButton.iconLeft, (height - icon->height()) / 2); }, button->lifetime()); - const auto user = bot.user; const auto weak = Ui::MakeWeak(container); button->setAcceptBoth(true); button->clicks( diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index bf01a5465..45b2c393d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -689,6 +689,7 @@ void SessionNavigation::applyBoost( done(false); return; } + auto slot = int(); auto different = PeerId(); auto earliest = TimeId(-1); const auto now = base::unixtime::now(); @@ -699,13 +700,13 @@ void SessionNavigation::applyBoost( ? peerFromMTP(*data.vpeer()) : PeerId(); if (!peerId && cooldown <= now) { - applyBoostChecked(channel, done); + applyBoostChecked(channel, data.vslot().v, done); return; - } else if (peerId != channel->id) { + } else if (peerId != channel->id + && (earliest < 0 || cooldown < earliest)) { + slot = data.vslot().v; different = peerId; - if (earliest < 0 || cooldown < earliest) { - earliest = cooldown; - } + earliest = cooldown; } } if (different) { @@ -731,7 +732,7 @@ void SessionNavigation::applyBoost( done(false); } else { const auto peer = _session->data().peer(different); - replaceBoostConfirm(peer, channel, done); + replaceBoostConfirm(peer, channel, slot, done); } } else { uiShow()->show(Ui::MakeConfirmBox({ @@ -752,11 +753,12 @@ void SessionNavigation::applyBoost( void SessionNavigation::replaceBoostConfirm( not_null from, not_null channel, + int slot, Fn done) { const auto forwarded = std::make_shared(false); const auto confirmed = [=](Fn close) { *forwarded = true; - applyBoostChecked(channel, done); + applyBoostChecked(channel, slot, done); close(); }; const auto box = uiShow()->show(Box([=](not_null box) { @@ -785,10 +787,11 @@ void SessionNavigation::replaceBoostConfirm( void SessionNavigation::applyBoostChecked( not_null channel, + int slot, Fn done) { _api.request(MTPpremium_ApplyBoost( - MTP_flags(0), - MTPVector(), // slots + MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots), + MTP_vector({ MTP_int(slot) }), channel->input )).done([=](const MTPpremium_MyBoosts &result) { done(true); @@ -854,7 +857,8 @@ void SessionNavigation::showRepliesForMessage( auto memento = std::make_shared( history, rootId, - commentId); + commentId, + params.highlightPart); memento->setFromTopic(topic); showSection(std::move(memento), params); return; diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 367be098f..2ae888937 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -166,6 +166,7 @@ struct SectionShow { return copy; } + TextWithEntities highlightPart; Way way = Way::Forward; anim::type animated = anim::type::normal; anim::activation activation = anim::activation::normal; @@ -317,9 +318,11 @@ private: void replaceBoostConfirm( not_null from, not_null channel, + int slot, Fn done); void applyBoostChecked( not_null channel, + int slot, Fn done); const not_null _session; diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index d1c11bd1d..c39d9b3c3 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1211,7 +1211,7 @@ stage('crashpad', """ mac: git clone https://github.com/desktop-app/crashpad.git cd crashpad - git checkout 171b601938 + git checkout f07f49e287 git submodule init git submodule update third_party/mini_chromium ZLIB_PATH=$USED_PREFIX/include diff --git a/Telegram/build/version b/Telegram/build/version index de406d1d2..1c347fd92 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4011001 +AppVersion 4011003 AppVersionStrMajor 4.11 -AppVersionStrSmall 4.11.1 -AppVersionStr 4.11.1 +AppVersionStrSmall 4.11.3 +AppVersionStr 4.11.3 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.11.1 +AppVersionOriginal 4.11.3 diff --git a/changelog.txt b/changelog.txt index c201151d7..b053efcf0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,17 @@ +4.11.3 (02.11.23) + +- Fix adding a link to media captions in scheduled / comments. +- Fix crash in link preview options saving. +- Fix possible crash in statistics. + +4.11.2 (01.11.23) + +- Highlight quoted parts in jump-to-message from replies. +- Ctrl+Click on message field reply bar to jump to message. +- Fix empty link preview displaying when generation failed. +- Fix external replies in topic groups. +- Allow enabling legacy tray icon on Windows. + 4.11.1 (29.10.23) - Fix crash in emoji status select.